English 中文(简体)
How to advance a progress bar from a recursive method while avoiding threading issues?
原标题:

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:	est", @"C:	est2");
        }


        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();
        }



    }
}
问题回答

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:	est", @"C:	est2", 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.

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.





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

热门标签