BackgroundWorker RunWorkerCompleted 事件

Posted

技术标签:

【中文标题】BackgroundWorker RunWorkerCompleted 事件【英文标题】:BackgroundWorker RunWorkerCompleted Event 【发布时间】:2011-02-17 21:45:59 【问题描述】:

我的 C# 应用程序有几个后台工作人员。有时,一名后台工作人员会解雇另一名工作人员。当第一个后台工作人员完成并触发RunWorkerCompleted 事件时,该事件将在哪个线程上触发,UI 或第一个后台工作人员从哪个RunWorkerAsync 被调用?我正在使用 Microsoft Visual C# 2008 Express Edition。您可能有的任何想法或建议将不胜感激。谢谢。

【问题讨论】:

【参考方案1】:

如果 BackgroundWorker 是从 UI 线程创建的,那么 RunWorkerCompleted 事件也会在 UI 线程上引发。

如果它是从后台线程创建的,则该事件将在未定义的后台线程上引发(不一定是同一个线程,除非您使用自定义 SynchronizationContext)。

有趣的是,这似乎并没有在 MSDN 上得到很好的记录。我能找到的最佳参考是here:

在您的应用程序中实现多线程的首选方法是使用BackgroundWorker 组件。 BackgroundWorker 组件使用事件驱动模型进行多线程处理。 后台线程运行您的DoWork 事件处理程序,创建控件的线程运行您的ProgressChanged 和RunWorkerCompleted 事件处理程序。您可以从ProgressChanged 和@987654328 调用您的控件@ 事件处理程序。

【讨论】:

这是不正确的。如果 UI 线程创建了 BGW 实例,它只会在 UI 线程上引发。如果一个线程创建了 BGW,那么它将在任意线程池线程上引发。 “将在同一个线程上引发”,如果不是在 UI 线程上创建则不是这种情况。编组对任意线程的调用是不可能的,只有 UI 线程具有所需的管道。需要 WindowsFormsSynchronizationContext 或 DispatcherSynchronizationContext 提供程序,默认提供程序 (SynchronizationContext) 在线程池线程上进行回调。 @Hans:好的,我已经说得更清楚了。只要我们在吹毛求疵,它就没有必须成为 UI 线程;您可以创建自己的SynchronizationContext确实同步。 另外,我们很困惑,因为您还必须从主线程调用 RunWorkerAsync。这是最重要的。我们有一个计时器来收集 BGW 并将它们启动。 System.Timers.Timer 和 System.Windows.Forms.Timer 之间的区别在于从主线程和随机线程调用 OnWorkerCompleted 之间的区别。 System.Timers.Timer 事件不在主线程上调用。 @TobiasKnauss:不,当前版本是正确的。回调在同一个同步上下文中运行,而不是同一个线程。如果RunWorkerAsync 是从一个随机的ThreadPool 线程调用的,我几乎可以保证回调不会在同一个线程上运行。【参考方案2】:

据我观察,RunWorkerCompleted 是在调用 RunWorkerAsync 的线程上执行的,不一定是创建后台工作线程的线程。

【讨论】:

【参考方案3】:

在同样的问题上发现了这个帖子msdn。

这都是关于 SynchronizationContext 的。

Windows 窗体将自动覆盖任何现有的 SynchronizationContext 在创建窗口时默认;看 WindowsFormsSynchronizationContext.AutoInstall: http://msdn.microsoft.com/en-us/library/system.windows.forms.windowsformssynchronizationcontext.autoinstall.aspx

BackgroundWoker 捕获 SynchronizationContext 时 RunWorkerAsync 被调用,而不是在它被构造的时候(注意这个 是一个实现细节,没有记录)。然后它使用 捕获 SynchronizationContext 以执行 RunWorkerCompleted。

所以,运行 RunWokerCompleted 的线程实际上是由 调用 RunWorkerAsync 时的 SynchronizationContext.Current。

WPF 提供了一个 DispatcherSynchronizationContext 来编组 调用它的 UI 线程。 Windows 窗体提供了一个 WindowsFormsSynchronizationContext 将封送对其的调用 用户界面线程。在进行 WPF/Forms 互操作时,两个系统共享一个 用户界面线程。您提到这都是在 MFC 应用程序的上下文中;在 在这种情况下,MFC 不提供 SynchronizationContext(当然), 但它会与 WPF 和 Forms 线程共享它的线程(它们都 共享一个 UI 线程)。

还有一条信息:默认的 SynchronizationContext 将 队列操作(例如,RunWorkerCompleted)到 ThreadPool。这 听起来像你看到的行为。此默认行为开始生效 如果 SynchronizationContext.Current 在 RunWorkerAsync 时为空 被调用。

所以,听起来关闭 Windows 窗体可能正在清除 SynchronizationContext.Current。 Windows 窗体确实有 一个“主要形式”,所以它可能会这样做,因为它认为最后一种形式 刚刚关闭的应用程序。

我建议:1) 测试 SynchronizationContext.Current 是否真的是 在调用 RunWorkerCompleted 时为 null,并且可能还有 在显示 Windows 窗体窗体之前和之后进行测试。只为了 确保这是问题所在。 2) 设置 SynchronizationContext.Current(通过 SynchronizationContext.SetSynchronizationContext) 在 Windows 之后 表格表格关闭。将“new DispatcherSynchronizationContext()”作为 论据。

或者,如果 Windows 窗体窗体是模式对话框,您可以 使用 Nito.Async 库中的 ScopedSynchronizationContext 类 (http://www.codeplex.com/NitoAsync),这是一个非常简单的使用类 临时替换 SynchronizationContext.Current,将其重置为 其“使用”块末尾的原始值。

关于各种 SynchronizationContext 类型的更多信息在我的 博客: http://nitoprograms.blogspot.com/2009/10/synchronizationcontext-properties.html

。总之插入

AsyncOperationManager.SynchronizationContext = new DispatcherSynchronizationContext(this.Dispatcher)

在 RunWorkerAsync 调用之前。

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。

以上是关于BackgroundWorker RunWorkerCompleted 事件的主要内容,如果未能解决你的问题,请参考以下文章

BackgroundWorker控件使用

C# BackGroundWorker backgroundWorker1_DoWork中,按钮不能按的问题

将 'BackgroundWorker' 替换为 'Thread'

winform BackgroundWorker的使用

简单多线程BackgroundWorker

backgroundWorker1