views:

286

answers:

2

I made this code example which successfully uses a BackgroundWorker to advance a progress bar in a for loop.

Now I'm trying to adapt it to work with the following recursive file copy method so that it shows me how far along the copying is, but the following code is giving me the error "This BackgroundWorker is currently busy and cannot run multiple tasks concurrently."

What do I need to change so that this recursive method does not get these threading issues?

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel LastChildFill="True" HorizontalAlignment="Left" VerticalAlignment="Top"
                Margin="10">

        <TextBlock DockPanel.Dock="Top" Text="{Binding PageTitle}" Style="{DynamicResource PageTitleStyle}"/>
        <TextBlock DockPanel.Dock="Top" Text="{Binding PageDescription}" Style="{DynamicResource PageDescriptionStyle}"/>

        <Button x:Name="Button_Start" HorizontalAlignment="Left"  DockPanel.Dock="Top" Content="Start Task" Click="Button_Start_Click" Height="25" Width="200"/>

        <ProgressBar x:Name="ProgressBar"
                     HorizontalAlignment="Left"
                    Margin="0 10 0 0"
                    Height="23"
                     Width="500"
                     Minimum="0"
                     Maximum="100"
                     />
    </DockPanel>
</Window>

code-behind:

using System.Windows;
using System.ComponentModel;
using System.Threading;
using System.IO;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private BackgroundWorker backgroundWorker;
        int thread1percentageFinished = 0;

        private int totalFilesToCopy;
        private int numberOfFilesCopied;

        public MainWindow()
        {
            InitializeComponent();

            ProgressBar.Visibility = Visibility.Collapsed;
        }

        private void Button_Start_Click(object sender, RoutedEventArgs e)
        {
            int totalFilesToCopy = 1000;
            int numberOfFilesCopied = 0;

            backgroundWorker = new BackgroundWorker();
            backgroundWorker.WorkerReportsProgress = true;
            backgroundWorker.WorkerSupportsCancellation = true;
            ProgressBar.Visibility = Visibility.Visible;

            CopyFolder(@"C:\test", @"C:\test2");
        }


        void CopyFolder(string sourceFolder, string destFolder)
        {
            backgroundWorker.DoWork += (s, args) =>
            {
                BackgroundWorker worker = s as BackgroundWorker;
                if (!Directory.Exists(destFolder))
                    Directory.CreateDirectory(destFolder);
                string[] files = Directory.GetFiles(sourceFolder);
                foreach (string file in files)
                {
                    string name = Path.GetFileName(file);
                    string dest = Path.Combine(destFolder, name);
                    File.Copy(file, dest, true);
                    numberOfFilesCopied++;
                    float percentageDone = (numberOfFilesCopied / (float)totalFilesToCopy) * 100f;
                    worker.ReportProgress((int)percentageDone);
                }
                string[] folders = Directory.GetDirectories(sourceFolder);
                foreach (string folder in folders)
                {
                    string name = Path.GetFileName(folder);
                    string dest = Path.Combine(destFolder, name);
                    CopyFolder(folder, dest);
                }
            };

            backgroundWorker.ProgressChanged += (s, args) =>
            {
                thread1percentageFinished = args.ProgressPercentage;
                ProgressBar.Value = thread1percentageFinished;
            };


            backgroundWorker.RunWorkerCompleted += (s, args) =>
            {
                Button_Start.IsEnabled = true;
                ProgressBar.Visibility = Visibility.Collapsed;
                ProgressBar.Value = 0;
            };

            backgroundWorker.RunWorkerAsync();
        }



    }
}
A: 

The problem is that you are invoking the same background worker for each iteration of the directory walk. All you need is one background worker that actually does the work. The entire recursive walk should be contained within a single call to DoWork (instead of each sub-directory scan). Then you just have the main UI thread updating your progress bar.

j0rd4n
+2  A: 

It would actually be better to use recursion to find all of the work you need done, then set up the BackgroundWorker, then start the work in a non-recursive loop, updating progress as needed, then close out the BackgroundWorker.

The way you're doing it, the progress bar will be jumping all over the place and you'll be providing unreliable feedback to the user. They might get to the very "end" of a copy and then suddenly recurse down into a very large folder structure, pushing the bar all the way back to the beginning.

If you want to keep doing it the way you are, though, you need to move all of the preparation of the BackgroundWorker to your Button_Start_Click method, as well as the RunWorkerAsync() call. You retain all of the "guts" of your existing CopyFolder's DoWork definition in CopyFolder itself, but instead pass in a reference to the background worker:

backgroundWorker.DoWork += (s, args) => 
{
    CopyFolder(@"C:\test", @"C:\test2", s);
};

Then you can have a CopyFolder that looks more like this:

void CopyFolder(string sourceFolder, string destFolder, BackgroundWorker worker)
{
    if (!Directory.Exists(destFolder))
        ... // the rest is unchanged
}

This way you're only creating and starting up a single BackgroundWorker and just passing a reference to it down the call tree.

mcl