多线程服务,BackgroundWorker和ThreadPool有什么区别?

27
我有一个.NET 3.5的Windows服务。我正在使用一个小应用程序进行测试,该应用程序在启动线程后仅使线程休眠,在300到6500毫秒的随机时间段内。我有几个关于这个问题的问题。
  1. BackgroundWorker是否真的只适用于WinForms应用程序,还是这只是胡说八道,它是如何调整到这种效果的?
  2. 我已经在这个问题这个问题中阅读了关于ThreadPool的信息。我不确定线程持续几秒钟是否对我造成了多大的问题。这个理由足够让我寻找其他解决方案吗?
  3. 最好自己创建后台线程吗?
现实生活中的服务将轮询数据库以获取待处理请求列表,为每个请求执行线程(限制并发线程的数量),每个线程将检查数据库中是否存在某些数据,如果存在则检索它,否则从流媒体API下载它,存储数据并返回该数据。下载将是消耗最多时间的部分。
我希望得到针对.NET 3.5 Framework的答案,但如果在.NET 4.0下实现此目标的更好或更有效的方法,则也想了解。欢迎提供更多信息的链接。
5个回答

35

BackgroundWorker的价值在于,它可以在创建实例的线程上引发ProgressChanged和RunworkerCompleted事件。这使得它在不能支持自由线程的程序中非常方便。

但是,为了使其正常工作,需要确保SynchronizationContext.Current属性引用了一个非默认的同步提供程序。这个提供程序负责将调用从一个线程传递到另一个线程。.NET框架有两个可用的提供程序:System.Windows.Forms.WindowsFormsSynchronizationContext和System.Windows.Threading.DispatcherSynchronizationContext。它们分别处理Winforms和WPF的同步。

Winforms和WPF都是类库,存在线程问题。它们都实现GUI,并且基于Windows的图形用户界面本质上是线程不安全的。Windows窗口只能从创建它们的线程更新。换句话说,这些自定义同步提供程序的存在是因为急需它们。还值得注意的是,它们利用UI线程的工作方式。UI线程以事件驱动的方式执行代码,在消息循环中进行通知。同步提供程序可以使用这种机制注入事件处理程序的调用。这并非偶然。

回到主题,Windows服务没有这样的功能。它没有GUI,也没有安装自定义同步提供程序。因此,BackgroundWorker在服务中提供的功能没有用处。如果没有自定义同步提供程序,缺省提供程序只会在线程池线程上运行事件。这是没有用的,你可能还不如从工作者线程触发事件。要让事件在另一个特定的线程中运行非常困难,除非重新创建消息循环机制或者连接Winforms管道并使用一个无形窗口创建模拟的UI线程。顺便说一下,这种做法并不罕见。


1
因此,BackgroundWorker 在我的服务中在 ThreadPool 下运行,如果我从 BackgroundWorkers 切换到 ThreadPool,我将获得几乎相同的结果? - bevacqua
2
是的,你没有获得服务中的附加值。 - Hans Passant
太好了!我担心线程不会被重复使用,必须自己实现线程池... - bevacqua

11

BackgroundWorker旨在简化在后台线程中运行的任务与UI的交互。您可以在BackgroundWorker vs background Thread上看到对何时使用BackGroundWorker、ThreadPool和简单Thread的很好的回答。

我认为它回答了这个问题:)。


我本以为我有一些可以添加到这个讨论中的东西,但实际上我的答案已经在那个问题的第一个答案里了。 - Ivan Ičin

8

我在博客上写了一篇关于异步后台任务不同实现方式的相当详尽的概述(链接)。 总结如下:最好选择Task;其次是BackgroundWorker;只有在真正需要时才使用ThreadThreadPool.QueueUserWorkItem

原因是:更容易检测和恢复错误,并且更容易与UI同步。

回答您的具体问题:

BackgroundWorker适用于任何主机,包括WinForms和WPF(甚至包括ASP.NET!),因为它基于。 Windows服务没有SynchronizationContext,但您可以使用Nito.Async库中的ActionThread,该库附带一个SynchronizationContext

如果我正确理解了你的问题,你目前拥有线程,并正在考虑使用线程池和后台工作者。在这些选择中,我建议使用后台工作者,但如果有机会的话,可以使用.NET 4.0中的新Task类(如果安装了Microsoft Rx,也可以在.NET 3.5中使用Task)。

我目前使用 BackgroundWorker,正在考虑使用 ThreadPool、自己的 Thread 和/或 Task - bevacqua
我建议您考虑使用“任务(Task)”,因为它们更像是“工作单元”的概念,可以很好地与线程池一起使用,而“BackgroundWorker”和“Thread”则是实际的线程。 - Stephen Cleary
这篇博客文章写得非常好,内容丰富,感谢你提供的信息和撰写比较所花费的时间! - M. Mimpen

3

对于服务,绝不要使用Backgroundworker。你应该使用System.Threading.Tasks命名空间中的Tasks,还可以使用任务并行线程执行。

http://msdn.microsoft.com/en-us/library/dd460717.aspx
我引用一下:“从.NET Framework 4开始,TPL是编写多线程和并行代码的首选方式。”

一些阅读材料:
http://msdn.microsoft.com/en-us/library/dd997413%28VS.100%29.aspx

从这里开始:
http://msmvps.com/blogs/brunoboucard/archive/2010/04/09/parallel-programming-task-oriented-parallel-part-1.aspx
http://msmvps.com/blogs/brunoboucard/archive/2010/05/18/parallel-programming-in-c-4-0-task-oriented-parallel-programming-part-2.aspx
http://msmvps.com/blogs/brunoboucard/archive/2010/11/06/parallel-programming-with-c-4-0-part-3.aspx

更多例子:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=489
http://www.dotnetfunda.com/articles/article984-parallel-compting-in-csharp-40-.aspx


请注意,目前我无法针对 .NET 4.0 框架进行定位。 - bevacqua

2
我认为你的第三个选择是最好的。我在.NET 3.5中使用Windows服务做过类似的事情,并发现创建自己的线程是一个不错的选择,特别是对于与Web服务进行交互的线程。
我创建了一个工作实例,并给它一个回调函数,在完成时向服务发送信号。我将准备运行的线程存储在一个队列中,并根据我想要的最大并发线程数将它们逐个取出。如果你只关心正在运行的服务数量,可以用一个简单的计数器来跟踪它们。我更喜欢将每个正在运行的工作实例存储在一个以线程的ManagedThreadId为键的字典中,这样我就可以方便地通知每个实例,如果我想要清理关闭它。这也方便了轮询运行实例以检查状态。

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