Search code examples
c#wpfbackgroundworkerwaitautoresetevent

Using BackgroundWorker to complete two methods one after the other WPF/C#


In my program I have two methods that takes a while to complete, about few minutes each. While these methods are being executed, I display a Progress Bar in a separate window which shows the progress of each method. My two methods are in a static Utility class. They look like the following:

public static class Utility
{
    public static bool TimeConsumingMethodOne(object sender)
    {
        for (int i = 1; i <= 100; i++)
        {
            Thread.Sleep(100);
            (sender as BackgroundWorker).ReportProgress(i);
        }
        return true;
    }

    public static bool TimeConsumingMethodTwo(object sender)
    {
        for (int i = 1; i <= 100; i++)
        {
            Thread.Sleep(50);
            (sender as BackgroundWorker).ReportProgress(i);
        }
        return true;
    }
}

Reading through similar questions in SO I learned that I should use BackgroundWorker and used the RunWorkerCompleted() to see when the worker completes its work. So, in my Main() I used BackgroundWorer() and subscribed to the RunWorkerCompleted() method. My goal here is to run the TimeConsumingMethodOne() first (and display progress while running), then once finished, run TimeConsumingMethodTwo() and show progress again, and when that's completed output the message box (which simulates some other work in my program). My Main() looks like the following:

public partial class MainWindow : Window
{
    public enum MethodType
    {
        One,
        Two
    }

    private BackgroundWorker worker = null;
    private AutoResetEvent _resetEventOne = new AutoResetEvent(false);
    private AutoResetEvent _resetEventTwo = new AutoResetEvent(false);

    private ProgressBarWindow pbWindowOne = null;
    private ProgressBarWindow pbWindowTwo = null;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void btnRun_Click(object sender, RoutedEventArgs e)
    {
        RunMethodCallers(sender, MethodType.One);
        _resetEventOne.WaitOne();
        RunMethodCallers(sender, MethodType.Two);
        _resetEventTwo.WaitOne();
        MessageBox.Show("COMPLETED!");
    }

    private void RunMethodCallers(object sender, MethodType type)
    {
        worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        switch (type)
        {
            case MethodType.One:
                worker.DoWork += MethodOneCaller;
                worker.ProgressChanged += worker_ProgressChangedOne;
                worker.RunWorkerCompleted += worker_RunWorkerCompletedOne;
                break;
            case MethodType.Two:
                worker.DoWork += MethodTwoCaller;
                worker.ProgressChanged += worker_ProgressChangedTwo;
                worker.RunWorkerCompleted += worker_RunWorkerCompletedTwo;
                break;
        }
        worker.RunWorkerAsync();
    }

    
    private void MethodOneCaller(object sender, DoWorkEventArgs e)
    {
        Dispatcher.Invoke(() =>
        {
            pbWindowOne = new ProgressBarWindow("Running Method One");
            pbWindowOne.Owner = this;
            pbWindowOne.Show();
        });

        Utility.TimeConsumingMethodOne(sender);
    }

    private void MethodTwoCaller(object sender, DoWorkEventArgs e)
    {
        Dispatcher.Invoke(() =>
        {
            pbWindowTwo = new ProgressBarWindow("Running Method Two");
            pbWindowTwo.Owner = this;
            pbWindowTwo.Show();
        });

        Utility.TimeConsumingMethodTwo(sender);
    }

    private void worker_RunWorkerCompletedOne(object sender, RunWorkerCompletedEventArgs e)
    {
        _resetEventOne.Set();
    }

    private void worker_RunWorkerCompletedTwo(object sender, RunWorkerCompletedEventArgs e)
    {
        _resetEventTwo.Set();
    }

    private void worker_ProgressChangedOne(object sender, ProgressChangedEventArgs e)
    {
        pbWindowOne.SetProgressUpdate(e.ProgressPercentage);
    }

    private void worker_ProgressChangedTwo(object sender, ProgressChangedEventArgs e)
    {
        pbWindowTwo.SetProgressUpdate(e.ProgressPercentage);
    }
}

Now the problem I have is, when I use _resetEventOne.WaitOne(); the UI hangs. If I removed those two waits, both methods run asynchronously, and the execution moves on and outputs the MessageBox even before those two methods complete.

What am I doing wrong? How do I get the program to finish my first BackgroundWorker and then move onto the next, and then when that's done, output the MessageBox?


Solution

  • Now the problem I have is, when I use _resetEventOne.WaitOne(); the UI hangs. If I removed those two waits, both methods run asynchronously and the execution moves on and outputs the MessageBox even before those two methods complete.

    What am I doing wrong?

    When you call WaitOne(), you are blocking the UI thread, causing the UI to hang. If you remove that call, then of course you start both workers at once.

    There are several different ways to approach your question. One is to stick as closely to your current implementation, and just fix the barest minimum to get it to work. Doing that, what you'll need to do is perform the actual next statement in the RunWorkerCompleted handler, instead of using an event to wait for the handler to execute.

    That looks like this:

    public partial class MainWindow : Window
    {
        public enum MethodType
        {
            One,
            Two
        }
    
        private BackgroundWorker worker = null;
    
        private ProgressBarWindow pbWindowOne = null;
        private ProgressBarWindow pbWindowTwo = null;
    
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void btnRun_Click(object sender, RoutedEventArgs e)
        {
            RunMethodCallers(sender, MethodType.One);
        }
    
        private void RunMethodCallers(object sender, MethodType type)
        {
            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            switch (type)
            {
                case MethodType.One:
                    worker.DoWork += MethodOneCaller;
                    worker.ProgressChanged += worker_ProgressChangedOne;
                    worker.RunWorkerCompleted += worker_RunWorkerCompletedOne;
                    break;
                case MethodType.Two:
                    worker.DoWork += MethodTwoCaller;
                    worker.ProgressChanged += worker_ProgressChangedTwo;
                    worker.RunWorkerCompleted += worker_RunWorkerCompletedTwo;
                    break;
            }
            worker.RunWorkerAsync();
        }
    
        private void MethodOneCaller(object sender, DoWorkEventArgs e)
        {
            Dispatcher.Invoke(() =>
            {
                pbWindowOne = new ProgressBarWindow("Running Method One");
                pbWindowOne.Owner = this;
                pbWindowOne.Show();
            });
    
            Utility.TimeConsumingMethodOne(sender);
        }
    
        private void MethodTwoCaller(object sender, DoWorkEventArgs e)
        {
            Dispatcher.Invoke(() =>
            {
                pbWindowTwo = new ProgressBarWindow("Running Method Two");
                pbWindowTwo.Owner = this;
                pbWindowTwo.Show();
            });
    
            Utility.TimeConsumingMethodTwo(sender);
        }
    
        private void worker_RunWorkerCompletedOne(object sender, RunWorkerCompletedEventArgs e)
        {
            RunMethodCallers(sender, MethodType.Two);
        }
    
        private void worker_RunWorkerCompletedTwo(object sender, RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show("COMPLETED!");
        }
    
        private void worker_ProgressChangedOne(object sender, ProgressChangedEventArgs e)
        {
            pbWindowOne.SetProgressUpdate(e.ProgressPercentage);
        }
    
        private void worker_ProgressChangedTwo(object sender, ProgressChangedEventArgs e)
        {
            pbWindowTwo.SetProgressUpdate(e.ProgressPercentage);
        }
    }
    

    That said, BackgroundWorker has been made obsolete by the newer task-based API with async and await. With some small changes to your code, it can be adapted to use that newer idiom:

    public partial class MainWindow : Window
    {
        public enum MethodType
        {
            One,
            Two
        }
    
        private ProgressBarWindow pbWindowOne = null;
        private ProgressBarWindow pbWindowTwo = null;
    
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private async void btnRun_Click(object sender, RoutedEventArgs e)
        {
            await RunMethodCallers(sender, MethodType.One);
            await RunMethodCallers(sender, MethodType.Two);
            MessageBox.Show("COMPLETED!");
        }
    
        private async Task RunMethodCallers(object sender, MethodType type)
        {
            IProgress<int> progress;
    
            switch (type)
            {
                case MethodType.One:
                    progress = new Progress<int>(i => pbWindowOne.SetProgressUpdate(i));
                    await Task.Run(() => MethodOneCaller(progress));
                    break;
                case MethodType.Two:
                    progress = new Progress<int>(i => pbWindowTwo.SetProgressUpdate(i));
                    await Task.Run(() => MethodTwoCaller(progress));
                    break;
            }
        }
    
        private void MethodOneCaller(IProgress<int> progress)
        {
            Dispatcher.Invoke(() =>
            {
                pbWindowOne = new ProgressBarWindow("Running Method One");
                pbWindowOne.Owner = this;
                pbWindowOne.Show();
            });
    
            Utility.TimeConsumingMethodOne(progress);
        }
    
        private void MethodTwoCaller(IProgress<int> progress)
        {
            Dispatcher.Invoke(() =>
            {
                pbWindowTwo = new ProgressBarWindow("Running Method Two");
                pbWindowTwo.Owner = this;
                pbWindowTwo.Show();
            });
    
            Utility.TimeConsumingMethodTwo(progress);
        }
    }
    

    To do the above does require a small adjustment to the Utility class as well:

    static class Utility
    {
        public static bool TimeConsumingMethodOne(IProgress<int> progress)
        {
            for (int i = 1; i <= 100; i++)
            {
                Thread.Sleep(100);
                progress.Report(i);
            }
            return true;
        }
    
        public static bool TimeConsumingMethodTwo(IProgress<int> progress)
        {
            for (int i = 1; i <= 100; i++)
            {
                Thread.Sleep(50);
                progress.Report(i);
            }
            return true;
        }
    }
    

    That is, the Progress<T> class takes the place of the BackgroundWorker.ProgressChanged event and ReportProgress() method.

    Note that with the above, the code has gotten significantly shorter, simpler, and is written in a more direct way (i.e. related statements are with each other in the same method now).

    The example you gave is necessarily simplified. That's perfectly fine, but it does mean that it's not known here what the Thread.Sleep() method represents. In fact, in many cases, this sort of thing can be refactored further such that only the long-running work is done asynchronously. This can sometimes simplify the progress-reporting even further, because it can be done after await-ing each individual asynchronously-executed work component.

    For example, let's suppose the work in the loop is either inherently asynchronous or is costly enough that it's reasonable to use Task.Run() to execute each loop iteration. For the purpose of the same, that can be represented using Task.Delay():

    static class Utility
    {
        public static async Task<bool> TimeConsumingMethodOne(Action<int> progress)
        {
            for (int i = 1; i <= 100; i++)
            {
                await Task.Delay(100);
                progress(i);
            }
            return true;
        }
    
        public static async Task<bool> TimeConsumingMethodTwo(Action<int> progress)
        {
            for (int i = 1; i <= 100; i++)
            {
                await Task.Delay(50);
                progress(i);
            }
            return true;
        }
    }
    

    In the above, I also don't use Progress<T>. Just a simple Action<int> delegate for the caller to use however they want.

    And with that change, your window code gets even simpler:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private async void btnRun_Click(object sender, RoutedEventArgs e)
        {
            await MethodOneCaller();
            await MethodTwoCaller();
            MessageBox.Show("COMPLETED!");
        }
    
        private async Task MethodOneCaller()
        {
            ProgressBarWindow pbWindowOne =
                new ProgressBarWindow("Running Method One") { Owner = this };
            pbWindowOne.Show();
    
            await Utility.TimeConsumingMethodOne(i => pbWindowOne.SetProgressUpdate(i));
        }
    
        private async Task MethodTwoCaller()
        {
            ProgressBarWindow pbWindowTwo =
                new ProgressBarWindow("Running Method Two") { Owner = this };
    
            pbWindowTwo.Show();
    
            await Utility.TimeConsumingMethodTwo(i => pbWindowTwo.SetProgressUpdate(i));
        }
    }
    

    Granted, I took the opportunity to remove the MethodType enum and just call the methods directly, which shortened the code even more. But even if all you did was avoid the use of Dispatcher.Invoke(), that still simplifies the code a lot.

    In addition to all that, if you were using data binding to represent the progress state instead of setting the value directly, WPF would handle the cross-thread invocation implicitly for you, so that the Progress<T> class isn't even required even if you can't refactor the Utility class code for it itself to be async.

    But, those are minor refinements compared to moving away from BackgroundWorker. I recommend doing that, but whether you invest time in those further refinements is less important.