如何让 BackgroundWorker ProgressChanged 事件按顺序执行?

Posted

技术标签:

【中文标题】如何让 BackgroundWorker ProgressChanged 事件按顺序执行?【英文标题】:How to make BackgroundWorker ProgressChanged events execute in sequence? 【发布时间】:2021-03-03 20:32:07 【问题描述】:

考虑以下代码:

private static BackgroundWorker bg = new BackgroundWorker();

static void Main(string[] args) 
  bg.DoWork += bg_DoWork;
  bg.ProgressChanged += bg_ProgressChanged;
  bg.WorkerReportsProgress = true;
  bg.RunWorkerAsync();

  Thread.Sleep(10000);


static void bg_ProgressChanged(object sender, ProgressChangedEventArgs e) 
  Console.WriteLine(e.ProgressPercentage);
  Thread.Sleep(100);
  Console.WriteLine(e.ProgressPercentage);


static void bg_DoWork(object sender, DoWorkEventArgs e) 
  for (int i = 0; i < 10; i++) 
    bg.ReportProgress(i);
  

运行时我得到以下输出:

0 1 1 2 0 3 2 3 5 4 4 6 5 7 7 8 6 9 8 9

我了解问题是 BackgroundWorker 为每次调用 ReportProgress 启动的线程之间的竞争条件。

如何确保每个 bg_ProgressChanged 的​​整个主体都按照我调用它们的顺序执行? 那就是我想得到 ​​p>

0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

结果。

【问题讨论】:

BackgroundWorker 并不是真的要在没有 UI 线程的情况下使用。 在增量中优先使用 ReportProgress 的 Ist 参数,而不是 userState 参数。建议您查看此 MSDN 链接以报告进度:msdn.microsoft.com/en-us/library/a3zbdb1t.aspx @Vijay 谢谢。修好了。 尝试将 ReportProgress 方法调用包装在 lock(_objectLock) 块中,例如lock(_lockObject) bg.ReportProgress(i); @Vijay:这根本不会有任何区别;这些调用在同一个线程上运行。 【参考方案1】:

BackgroundWorker 在调用 RunWorkerAsync() 的线程的当前 SynchronizationContext 上引发 ProgressChanged 事件。

默认的 SynchronizationContext 在 ThreadPool 上运行回调而不进行任何同步。

如果您在 UI 应用程序(WPF 或 WinForms)中使用 BackgroundWorker,它将使用该 UI 平台的 SynchronizationContext,它将按顺序执行回调。

【讨论】:

我已将示例创建为目标 .NET Framework 3.5 的控制台应用程序。我已将输出类型更改为 Windows 应用程序,它按照您的描述工作。 我的问题是我希望 ProgressChanged 事件的整个主体按照我调用它们的顺序执行。我已经更新了这个问题,以更好地反映我想做的事情。 @Andris:在 UI SynchronizationContext 下,它们将按顺序执行。 @SLaks:正如我所说,我已将应用程序输出类型更改为 Windows 应用程序,但请注意,在我修改后的示例的输出中,前 5 个在前 4 个之前,所以调用参数 5 在参数 4 之前执行。更改输出类型还不够吗?我不确定如何将 SynchronizationContext 设置为“UI SynchronizationContext”。我不熟悉该主题。 你需要调用Application.Run()来运行一个UI线程。 (这是一个阻塞调用)。项目类型根本不重要。然而,如果你不做 UI,你可能根本不应该使用 BackgroundWorker。【参考方案2】:

不要使用这个解决方案!!!正如 SLaks 指出的那样,可能会导致死锁。

我似乎偶然发现了一个答案。我通过以下方式更改了代码:

   [MethodImpl(MethodImplOptions.Synchronized)]
   static void bg_ProgressChanged(object sender, ProgressChangedEventArgs e) 
     Console.WriteLine(e.ProgressPercentage);
     Thread.Sleep(100);
     Console.WriteLine(e.ProgressPercentage);
   

现在我得到了我想要的输出:

0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

【讨论】:

在 UI 应用程序中,这没有什么区别,因为回调只会在 UI 线程上运行。 请注意,这可能会导致死锁。 [MethodImpl(MethodImplOptions.Synchronized)] 等价于 lock(this),不应使用。 @SLaks:很好的提示。再次感谢。

以上是关于如何让 BackgroundWorker ProgressChanged 事件按顺序执行?的主要内容,如果未能解决你的问题,请参考以下文章

是不是 .NET 4.0 TPL 让 APM、EAP 和 BackgroundWorker 异步模式过时了?

如何在点击触发的backgroundWorker中添加一段代码?

如何在窗体的关闭事件中停止 BackgroundWorker?

如何使用 GNU make 编译多个简单项目

如何正确实现 BackgroundWorker 以通知用户任务正在进行中

如何使用 C# BackgroundWorker 报告本机 C++ 代码的进度?