WPF BackgroundWorker与Dispatcher的区别

33

在我的WPF应用程序中,我需要执行异步操作,然后需要更新GUI。我需要在不同的时间使用不同的操作多次执行此操作。我知道两种方法可以实现:Dispatcher和BackgroundWorker。

因为一旦选择了一种方法就很难回头,所以我想问一下:哪种方法更好?选择其中一种方法的原因是什么?

谢谢! Pileggi


3
Pileggi是什么?:-s - Aditya Bokade
2个回答

40
Dispatcher 和其他线程方法的主要区别在于,Dispatcher 实际上并不是多线程的。Dispatcher 管理需要一个单独线程来正常工作的控件;Dispatcher 的 BeginInvoke 方法将事件排队以供后续执行(取决于优先级等),但仍在同一线程上执行。
另一方面,BackgroundWorker 实际上会在调用时在一个单独的线程中执行代码。它也比真正的线程更容易使用,因为它会自动与应用程序的主线程进行同步(至少我记得是这样),主线程负责控制和消息队列(在 WPF 和 Silverlight 中为 Dispatcher 线程),因此在后台线程更新控件时无需使用 Dispatcher.Invoke(或 WinForms 中的 Control.Invoke),虽然这可能并不总是推荐的做法。
如 Reed 所说,Task Parallel Library 是一个很好的替代选项。
编辑:进一步观察。
正如我之前所说,Dispatcher 实际上并不是多线程的;它只是给人一种错觉,因为它确实在其他时间运行您传递给它的委托。我只会在代码实际上只涉及应用程序的 View 方面时使用 Dispatcher - 即控件、页面、窗口等。当然,它的主要用途实际上是从其他线程触发操作以正确地更新控件或在正确时间更新控件(例如,在 WPF 中只有在某个控件完全呈现/布局之后才设置焦点最容易使用 Dispatcher,因为渲染并不是确定性的)。
BackgroundWorker 可以使多线程代码比通常更简单;它是一个容易理解的简单概念,最重要的是 (如果有意义的话)您可以从它派生自定义 Worker,这些 Worker 可以是执行单个任务异步的专门类,具有可用作参数、进度通知和取消等属性。我总是觉得 BackgroundWorker 很有帮助(除了当我必须从中派生出来以保持原始线程的 Culture 以正确维护本地化时 :P)。

最强大但也最难的方法是使用最低级别的System.Threading.Thread;然而,很容易犯错误,因此并不推荐。多线程编程很,这是肯定的。然而,如果您想了解所有方面,有很多好的信息可供参考:我们优秀的同行Jon Skeet所写的这篇优秀文章立即脱颖而出(该文章的最后一页还有许多非常有趣的链接)。

在.Net 4.0中,我们有一个不同的选择,即Task Parallel Library。我还没有多少经验,但从我看到的情况来看,它非常令人印象深刻(而PLINQ则非常出色)。如果您有好奇心和资源去学习它,那就是我建议的(毕竟并不需要太多时间来学习)。


@Alex Paven:谢谢!现在我必须做出选择。根据Reed Copsey的建议,使用ThreadPool和Dispatcher可能更简单;而你则认为这样做可能会降低性能。虽然我不需要在“紧密循环”中执行任何操作,但是选择还是很难!如果你是我,你会怎么选择呢? :-) - lamarmora
@Alex Paven:我可以告诉你,“使用线程池和调度程序可能更简单”是真的,因为这两种操作非常不同,方法需要不同数量的参数。BGW仅接受1个对象参数。我没有“你将需要多个BGW”的问题。在这种情况下,它可能会变得麻烦,因为我部分锁定应用程序,直到异步过程全部执行完毕,然后我可以始终使用相同的BGW。所有异步进程都是对Web服务的单个请求。最后,我担心您关于性能的警告。 - lamarmora
实际上,我认为你可以从BGW派生,并在派生对象上拥有一些属性(只读以保持安全性);构造函数可以接受参数,将它们放入属性中,DoWork事件可以使用它们。这实际上非常整洁地封装了一个线程操作(当然,并不适用于所有情况,但是这是一个不错的选择)。 - Alex Paven
你真的不需要担心性能 - 我甚至很抱歉提起它。我所谈论的项目中性能不佳的原因是(我非常确定)一些STA COM组件,我必须在多线程代码中使用它们(让UI保持锁定状态直到该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该该 慢速执行完成); 最终,多线程代码花费了约15秒,而单线程代码只需10秒,所有这样做只是为了在运行时有一个进度指示器。但是像往常一样,最好制作一个快速原型并查看其效果。 - Alex Paven
http://blogs.msdn.com/b/pigscanfly/archive/2009/08/06/stackoverflow-answer-why-learn-multi-core-programming-2.aspx 给出了一个很好的例子。就像我说的,这并不总是最好的方法,但总有最简单的选择:bgw.DoWork += (o, e) => MyRealMethod(param); :) - Alex Paven
显示剩余5条评论

8
BackgroundWorker在执行单个操作时非常好用,可以提供进度通知和完成事件。但是,如果要多次运行相同的操作或多个操作,则需要多个BackgroundWorker,这样会变得繁琐。
如果不需要进度事件,则使用线程池(ThreadPool)和调度程序(Dispatcher)可能更简单 - 尤其是如果要执行多个不同的操作。
然而,如果C# 4是一个选项,那么使用任务并行库(Task Parallel Library)也是一个很好的选择。这让您可以使用当前同步上下文(SynchronizationContext)设置的连续任务(continuation tasks),在许多情况下提供了一个更简单、更清晰的模型。有关详细信息,请参见我在此主题上的博客文章

不好意思,我正在使用VB 2008和fw 3.5 SP1。我将使用ThreadPool和Dispatcher。 - lamarmora
非常感谢!现在我对问题有了更清晰的“画面”。 - lamarmora

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