使用BackgroundWorker完成两个方法,一个接一个WPF/C# [英] Using BackgroundWorker to complete two methods one after the other WPF/C#

查看:70
本文介绍了使用BackgroundWorker完成两个方法,一个接一个WPF/C#的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的程序中,我有两种方法需要一段时间才能完成,每种方法大约需要几分钟.在执行这些方法时,我在一个单独的窗口中显示一个进度栏,该窗口显示了每种方法的进度.我的两个方法都在静态Utility类中.它们如下所示:

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

通过阅读SO中类似的问题,我了解到我应该使用BackgroundWorker,并使用RunWorkerCompleted()来查看工作程序何时完成工作.因此,在Main()中,我使用BackgroundWorer()并订阅了RunWorkerCompleted()方法.我的目标是首先运行TimeConsumingMethodOne()(并在运行时显示进度),然后运行一次,运行TimeConsumingMethodTwo()并再次显示进度,完成后输出消息框(模拟程序中的其他工作) .我的Main()如下所示:

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

现在我遇到的问题是,当我使用_resetEventOne.WaitOne();时. UI挂起.如果我删除了这两个等待,则这两个方法都将异步运行,并且即使在这两个方法完成之前,执行仍会继续执行并输出MessageBox.

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.

我做错了什么?如何使程序完成我的第一个BackgroundWorker,然后移至下一个,然后完成该操作,输出MessageBox?

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?

推荐答案

现在我遇到的问题是,当我使用_resetEventOne.WaitOne();时. UI挂起.如果我删除了这两个等待,则这两个方法都将异步运行,并且即使在这两个方法完成之前,执行仍会继续执行并输出MessageBox.

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.

我在做什么错了?

调用WaitOne()时,您正在阻塞UI线程,从而导致UI挂起.如果您删除该呼叫,那么当然您会立即启动两个工作人员.

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.

有几种方法可以解决您的问题.一种是尽可能地坚持当前的实现,只修复最低限度的最低要求,以使其正常工作.这样做,您需要做的是在RunWorkerCompleted处理程序中执行实际的下一条语句,而不是使用事件等待处理程序执行.

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.

看起来像这样:

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

也就是说,BackgroundWorker已被带有asyncawait的较新的基于任务的API淘汰.对您的代码进行一些小的更改,即可使其适应该较新的习惯用法:

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

要执行上述操作,还需要对Utility类进行少量调整:

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

也就是说,Progress<T>类代替了BackgroundWorker.ProgressChanged事件和ReportProgress()方法.

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).

您给出的示例必须简化.很好,但这确实意味着Thread.Sleep()方法代表的是这里.实际上,在许多情况下,此类事情可以进一步重构,以便仅长时间运行的工作是异步完成的.有时,这可以甚至进一步简化进度报告,因为可以在await-每个异步执行的工作组件之后完成.

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.

例如,让我们假设循环中的工作本质上是异步的,或者是足够昂贵的,因此可以合理地使用Task.Run()来执行每个循环迭代.出于相同的目的,可以使用Task.Delay()表示:

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

在上面,我也不使用Progress<T>.只是一个简单的Action<int>委托,供调用者根据需要使用.

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

当然,我借此机会删除了MethodType枚举,而直接调用了方法,这大大缩短了代码.但是,即使您所做的只是避免使用Dispatcher.Invoke(),仍然大大简化了代码.

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.

此外,如果您使用数据绑定来表示进度状态,而不是直接设置值,则WPF会为您隐式处理跨线程调用,因此Progress<T>类甚至不会即使您无法将Utility类代码本身重构为async,也是必需的.

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.

但是,与离开BackgroundWorker相比,这些只是次要的改进.我建议您这样做,但是是否需要花费时间进行进一步的改进就不那么重要了.

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.

这篇关于使用BackgroundWorker完成两个方法,一个接一个WPF/C#的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆