使用BackgroundWorker更新GUI

5
我发现一种很好的方法来执行后台工作并更新GUI是使用后台处理程序。但是,对于这个(愚蠢)的小任务(从1数到10000),它没有更新标签内容,而是打印到调试器!(当然,这只是另一个项目的尝试解决方案...)
下面是代码:
public partial class MainWindow : Window
{
    BackgroundWorker bw = new BackgroundWorker();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {

        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
        bw.WorkerReportsProgress = true;
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
        bw.RunWorkerAsync();

    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        MessageBox.Show("DONE");

    }

    void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        label1.Content = "going here: "+e.ProgressPercentage;
        Debug.WriteLine(e.ProgressPercentage);
    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i=0; i < 10000; i++)
        {
            bw.ReportProgress((i*100)/10000);
        }
    }

}
3个回答

12
ProgressChanged事件在UI线程上触发,而不是工作线程上。在您的代码中,工作线程几乎什么都没做(只是从0循环到10000并调用ReportProgress),大部分工作都在UI线程上完成。基本上,您发送了太多的进度通知。因此,UI线程几乎总是忙碌的,没有时间渲染标签的新内容。
在WPF中进行呈现时,当您更改控件属性时,不会立即执行,而是在单独的调度程序帧上执行,该帧在调度程序没有更紧急的任务要执行时处理,基于任务的优先级。用于呈现的优先级的值为7(DispatcherPriority.Render);ProgressChanged事件以优先级9(DispatcherPriority.Normal)驱动到UI线程上,如MSDN所述。因此,ProgressChanged通知始终具有比呈现更高的优先级,并且由于它们不断到来,调度程序永远没有时间处理呈现任务。
如果您只是降低通知的频率,则应用程序应该可以正常工作(当前为每个百分比值发送100个通知,这是无用的):
    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 0; i < 10000; i++)
        {
            if (i % 100 == 0)
                bw.ReportProgress(i / 100);
        }
    }

信不信由你,我在看到你的评论之前就已经在考虑这个问题了!UI 因为太多调用而被冻结是完全有道理的。 - Miguel Ribeiro
2
当然,从工作线程绑定到属性并更新它会更容易。 - EightyOne Unite
@Stimul8d,完全正确,这应该是在WPF中做事情的首选方式。 - Thomas Levesque

3
this.Dispatcher.BeginInvoke( (Action) delegate(){
   label1.Content = "going here: "+e.ProgressPercentage;
});

0
尝试使用类似以下方式更改标签:

string Text = "going here: " + e.ProgressPercentage;
this.Invoke((MethodInvoker)delegate {
    label1.Content = newText;
});

请注意,我不确定它是否有效。我现在无法测试它。如果它不起作用,请告诉我,我会删除答案。
如果您需要一种规范的方法来实现您想要的功能,请查看此帖子中Hath的答案:如何从另一个线程更新GUI?

嗯...如果我使用Windows表单,你的答案可能有效...但在WPF中,我没有从控件中获取Invoke属性...只有控件的分派器。我也一直在尝试“搞砸”它,但没有成功:( - Miguel Ribeiro
1
你可以在WPF中使用Dispatcher.Invoke完成相同的操作。但是这并不重要:ProgressChanged事件在UI线程上触发,因此不需要Invoke。 - Thomas Levesque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接