BackgroundWorker.ReportProgress()和Control.BeginInvoke()的区别

3
以下是1和2选项的区别:
1. 如果您选择选项1,则将使用默认设置来执行操作。 2. 如果您选择选项2,则可以自定义设置以执行操作。
    private void BGW_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i=1; i<=100; i++)
        {
            string txt = i.ToString();
            if (Test_Check.Checked)
                //OPTION 1
                Test_BackgroundWorker.ReportProgress(i, txt); 
            else
                //OPTION 2
                this.BeginInvoke((Action<int, string>)UpdateGUI, 
                                  new object[] {i, txt});
        }
    }

    private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        UpdateGUI(e.ProgressPercentage, (string)e.UserState);
    }

    private void UpdateGUI(int percent, string txt)
    {
        Test_ProgressBar.Value = percent;
        Test_RichTextBox.AppendText(txt + Environment.NewLine);
    }

看着反射器,Control.BeginInvoke() 方法似乎使用:

this.FindMarshalingControl().MarshaledInvoke(this, method, args, 1);

似乎最终会调用一些本地函数,如PostMessage(),但无法从反编译器中准确确定流程(讨厌的编译器goto优化)。

而BackgroundWorker.Invoke()似乎使用:

this.asyncOperation.Post(this.progressReporter, args);

这似乎最终会调用ThreadPool.QueueUserWorkItem()。

(我猜测每种情况下这些是相关的函数调用。) 如果我理解正确,使用线程池不会保证执行顺序,而使用Post机制则会。也许这将是一个潜在的区别?(编辑 - 我无法合成这样的情况 - 调用顺序似乎在两种情况下都得到保留,至少在我的简单测试中是如此。)

谢谢!

3个回答

2

一个重要的区别是,Control.Invoke 会一直阻塞直到 UpdateGUI 调用被执行并完成,但 BackgroundWorker.ReportProgress 不会阻塞(它会立即返回,在 BackgroundWorker 触发事件之前)。

如果你希望它们表现相同,可以调用不会阻塞的 Control.BeginInvoke


我的答案是一样的。ReportProgress是异步的,Control.Invoke是同步的。在进度条的情况下,选择其中一个可能没有明显的影响。如果你使用ReportProgress,GUI更新几乎是即时的 - 唯一的原因是如果你的表单正忙于处理一堆其他事件。 - MusiGenesis
是的,但我现在正在询问ReportProgress和BeginInvoke之间的区别。 - Ohad Schneider
如果您的UpdateGUI方法执行了大量耗时的工作,那么差异将会更加显著。那么您的选择就是在最大速度下运行后台进程但延迟GUI更新(ReportProgress),或者实时获取GUI更新但减慢后台进程(Control.Invoke)。 - MusiGenesis
当您更改为BeginInvoke时,反射器会显示什么?如果它导致与ReportProgress相同的代码,我不会感到惊讶。 - MusiGenesis
我在代码下面包含了我的(有限的)反射器发现 :) - Ohad Schneider
显示剩余2条评论

2
它们是完全相同的。你在BackgroundWorker中看到的调用使用了SynchronizationContext。事实上,Post()方法的默认实现使用线程池,但在启动Windows窗体应用程序时,默认的同步上下文被WindowsFormsSynchronizationContext替换,它实际上调用了Control.BeginInvoke()

0

我发现了一个显著的差异。在 BGW 正在运行时关闭表单会导致 this.Invoke() 和 this.BeginInvoke() 抛出 ObjectDisposedException 异常。BGW ReportProgress 机制规避了这个问题。为了兼顾两者的优点,以下模式效果很好

public partial class MyForm : Form
{
    private void InvokeViaBgw(Action action)
    {
        Packing_Worker.ReportProgress(0, action);
    }

    private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (this.IsDisposed) return; //You are on the UI thread now, so no race condition

        var action = (Action)e.UserState;
        action();
    }

    private private void BGW_DoWork(object sender, DoWorkEventArgs e)
    {
       //Sample usage:
       this.InvokeViaBgw(() => MyTextBox.Text = "Foo");
    }
}

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