WPF Dispatcher.BeginInvoke和UI /后台线程

时间:2009-07-30 16:44:32

标签: wpf multithreading user-interface dispatcher

我想我需要对WPF Dispatcher.Invoke Dispatcher.BeginInvoke 的使用做一些澄清。

假设我有一些长时间运行的“工作”代码,就像在简单的WPF应用程序中按下按钮一样:

longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
    {
    Console.WriteLine("Starting Work Action");
    int i = int.MaxValue;
    while (i > 0)
        i--;
    Console.WriteLine("Ending Work Action");
    longWorkTextBox.Text = "Work Complete";
    };
longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);

此代码在执行 workAction 时锁定了我的用户界面。这是因为Dispatcher调用总是在UI线程上运行,对吗?

假设这样,配置我的调度程序在我的UI的单独线程中执行 workAction 的最佳做法是什么?我知道我可以添加 BackgroundWorker 到我的 workAction 以阻止我的UI锁定:

longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += delegate
    {
        Console.WriteLine("Starting Slow Work");
        int i = int.MaxValue;
        while (i > 0)
        i--;
        Console.WriteLine("Ending Work Action");
    };
    worker.RunWorkerCompleted += delegate
    {
        longWorkTextBox.Text = "Work Complete";
    };
    worker.RunWorkerAsync();
 };
 longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);

除了使用 BackgroundWorker 之外,还有更优雅的方法吗?我一直听说 BackgroundWorker 很古怪,所以我很想知道一些替代方案。

5 个答案:

答案 0 :(得分:25)

老实说,我认为BackgroundWorker是最优雅的解决方案。我想不出更简单的方法。

答案 1 :(得分:4)

查理的回答是你正在寻找的,真的。

但是,如果可能的话,您可能会考虑是否可以将您的工作包括在内,以便各个工作单元都很小,并且不会对UI造成太大影响。这将允许您直接使用Dispatcher。在WPF线程页面上有一个很好的例子:https://msdn.microsoft.com/en-us/library/ms741870%28v=vs.100%29.aspx

答案 2 :(得分:4)

我也不喜欢BackgroundWorker。 一个简单的替代方案可能是:

using System;
using System.Threading;
using System.Windows;

namespace Sample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
             base.OnSourceInitialized(e);
             longWorkTextBox.Text = "Ready For Work!";
       }

        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            new Thread(Work).Start();
        }

        void Work()
        {
            longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Working..."; }));
            Console.WriteLine("Starting Work Action");
            int i = int.MaxValue;
            while (i > 0)
                i--;
            Console.WriteLine("Ending Work Action");
            longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Work Complete"; }));
        }
    }
}

容易,不是吗?

答案 3 :(得分:2)

正如其名称所示,它将在后台执行,因此您无需使用Dispatcher对其进行实例化。此外,如果您希望此代码运行到WP7中,则BeginInvoke不会获取背景参数。

我的建议是将BackgroundWorker创建为:

BackgroundWorker worker = new BackgroundWorker;

然后为事件创建处理程序:

worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork +=new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.ProgressChanged +=new ProgressChangedEventHandler(worker_ProgressChanged);

最后你打电话:

bkwkPlayingLoop.RunWorkerAsync();

从DoWork内部使用Dispatcher是一个很大的诱惑,而是调用worker.ReportProgress()并从那里处理UI。否则你将面临与解雇终止事件的一些不一致。

答案 4 :(得分:0)

任务比后台工作人员更容易使用,做更多事情,问题更少,并且几乎创建了这些任务,因此后台工作人员不再需要使用......