BackgroundWorker 与后台线程

Posted

技术标签:

【中文标题】BackgroundWorker 与后台线程【英文标题】:BackgroundWorker vs background Thread 【发布时间】:2010-12-03 04:08:02 【问题描述】:

关于我应该在 Windows 窗体应用程序上使用的后台线程实现的选择,我有一个风格问题。目前,我在一个具有无限 (while(true)) 循环的表单上有一个 BackgroundWorker。在这个循环中,我使用WaitHandle.WaitAny 来保持线程打盹,直到发生有趣的事情。我等待的事件句柄之一是“StopThread”事件,这样我就可以跳出循环。从我的覆盖Form.Dispose() 发出此事件。

我在某处读到BackgroundWorker 确实适用于您不想与 UI 绑定并具有有限结束的操作 - 例如下载文件或处理一系列项目。在这种情况下,“结束”是未知的,只有当窗口关闭时。因此,我是否更适合为此目的使用后台线程而不是 BackgroundWorker

【问题讨论】:

【参考方案1】:

我的一些想法...

    如果您有一个在后台运行且需要与 UI 交互的任务,请使用 BackgroundWorker。将数据和方法调用编组到 UI 线程的任务通过其基于事件的模型自动处理。避免 BackgroundWorker 如果... 您的程序集没有或不直接与 UI 交互, 你需要线程是前台线程,或者 您需要控制线程优先级。 如果需要效率,请使用ThreadPool 线程。 ThreadPool 有助于避免与创建、启动和停止线程相关的开销。避免使用 ThreadPool 如果... 任务在应用程序的整个生命周期内运行, 你需要线程是前台线程, 您需要控制线程优先级,或者 您需要线程具有固定标识(中止、挂起、发现)。 将Thread 类用于长时间运行的任务以及当您需要正式线程模型提供的功能时,例如,在前台和后台线程之间进行选择、调整线程优先级、对线程执行进行细粒度控制等。

【讨论】:

后台工作者位于 System.dll 程序集和 System.ComponentModel 命名空间中。不依赖于 Winforms。 没错,但BackgroundWorker 旨在向感兴趣的一方报告线程进度,这通常涉及UI。该类的 MSDN 文档非常清楚地说明了这一点。如果您只是需要在后台执行一项任务,最好使用ThreadPool 线程。 关于您关于System.Windows.Forms 程序集的观点; BackgroundWorker 对于 WPF 应用程序也很有用,这些应用程序可能没有对 WinForms 的引用。【参考方案2】:

根据我对您问题的理解,您使用 BackgroundWorker 作为标准线程。

BackgroundWorker 推荐用于你不想占用 UI 线程的事情的原因是因为它在进行 Win Forms 开发时会暴露一些不错的事件。

RunWorkerCompleted 之类的事件在线程完成它需要做的事情时发出信号,ProgressChanged 事件在线程进度上更新 GUI。

所以如果你使用这些,我认为使用标准线程来完成你需要做的事情没有任何害处。

【讨论】:

另一个我不确定的问题是,假设我正在尝试处理运行后台工作程序的表单。我发出关闭事件 (ManualResetEvent) 的信号,然后 DoWork 将正常退出。即使 DoWork 可能需要更长的时间才能完成,我是否应该让表单继续并 Dispose,或者是否有某种方法(并且更好)来线程。加入后台工作人员,直到它真的退出,然后让 Dispose的形式继续? 我认为 BackgroundWorker.IsBusy 就是您要在那里寻找的东西。 仅使用CancelAsync(如果您的线程将在短时间内轮询,则测试CancellationPending,如果您想引发异常,请使用System.Threading.Thread.Abort(),它会在内部引发异常线程块本身,根据情况选择合适的模型。【参考方案3】:

与马特戴维斯所说的差不多,还有以下几点:

对我来说,BackgroundWorker 的主要区别在于通过SynchronizationContext 自动编组已完成的事件。在 UI 上下文中,这意味着完成事件在 UI 线程上触发,因此可用于更新 UI。如果您在 UI 上下文中使用 BackgroundWorker,这是一个主要的区别。

通过ThreadPool 执行的任务不能轻易取消(这包括ThreadPoolQueueUserWorkItem 和委托异步执行)。因此,虽然它避免了线程启动的开销,但如果您需要取消,请使用 BackgroundWorker 或(更有可能在 UI 之外)启动线程并保留对它的引用,以便您可以调用 Abort()

【讨论】:

仅...希望应用程序设计为关于停止线程任务的 clean 方法(通常不是 Abort)【参考方案4】:

此外,您在后台工作人员的生命周期内占用了一个线程池线程,这可能会引起关注,因为它们的数量是有限的。我想说,如果您只为您的应用程序创建一次线程(并且不使用后台工作者的任何功能),那么请使用线程,而不是后台工作者/线程池线程。

【讨论】:

我认为这是一个很好的观点。因此,如果您在表单的生命周期内“临时”需要一个后台线程,那么我从中得到的消息是使用后台工作线程,但是如果您在表单的整个生命周期内需要一个后台线程(可能是几分钟、几小时、天...)然后使用线程而不是BackgroundWorker,以免滥用线程池的目的 关于:“......这可能会引起关注,因为它们的数量有限”,您的意思是操作系统上的其他应用程序可能需要它们并从同一个“池”共享吗? 【参考方案5】:

您知道,有时使用 BackgroundWorker 会更容易,无论您使用的是 Windows 窗体、WPF 还是其他技术。这些家伙的巧妙之处在于,您无需过多担心线程执行的位置即可获得线程,这对于简单的任务非常有用。

在使用BackgroundWorker 之前,首先考虑是否要取消线程(关闭应用程序、用户取消),然后您需要决定您的线程是否应该检查取消,或者是否应该将其推向执行本身。

BackgroundWorker.CancelAsync() 会将CancellationPending 设置为true,但不会再做任何事情,然后线程有责任不断检查这一点,请记住,在这种方法中,您最终可能会遇到竞争条件,其中您的用户已取消,但线程在测试 CancellationPending 之前已完成。

另一方面,Thread.Abort() 会在线程执行中抛出一个异常,强制取消该线程,但如果在执行过程中突然引发此异常,您必须小心可能会有危险。

无论什么任务,线程都需要非常仔细的考虑,进一步阅读:

Parallel Programming in the .NET Framework Managed Threading Best Practices

【讨论】:

【参考方案6】:

我在了解 .NET 之前就知道如何使用线程,所以当我开始使用 BackgroundWorkers 时需要一些时间来适应。 Matt Davis 非常出色地总结了差异,但我要补充一点,要准确理解代码在做什么更困难,这会使调试变得更加困难。 IMO,考虑创建和关闭线程比考虑将工作分配给线程池更容易。

我仍然无法评论其他人的帖子,所以请原谅我在使用答案来解决 piers7 时的一时跛脚

不要使用Thread.Abort(); 代替,发出事件信号并将您的线程设计为在发出信号时优雅地结束。 Thread.Abort() 在线程执行中的任意点引发ThreadAbortException,它可以做各种不愉快的事情,如孤立监视器、损坏的共享状态等等。http://msdn.microsoft.com/en-us/library/system.threading.thread.abort.aspx

【讨论】:

【参考方案7】:

我想指出一个尚未提及的 BackgroundWorker 类的行为。您可以通过设置 Thread.IsBackground 属性使普通线程在后台运行。

后台线程与前台线程相同,只是后台线程不会阻止进程终止。 [1]

您可以通过在表单窗口的构造函数中调用以下方法来测试此行为。

void TestBackgroundThread()

    var thread = new Thread((ThreadStart)delegate()
    
        long count = 0;
        while (true)
        
            count++;
            Debug.WriteLine("Thread loop count: " + count);
        
    );

    // Choose one option:
    thread.IsBackground = true; // <--- This will make the thread run in background
    thread.IsBackground = false; // <--- This will delay program termination

    thread.Start();

当 IsBackground 属性设置为 true 并关闭窗口时,您的应用程序将正常终止。

但是当 IsBackground 属性设置为 false(默认情况下)并且您关闭窗口时,只有窗口会消失,但进程仍会继续运行。

BackgroundWorker 类利用在后台运行的线程。

【讨论】:

【参考方案8】:

如果它没有坏 - 修复它直到它......开玩笑:)

但是说真的,BackgroundWorker 可能与您已经拥有的非常相似,如果您从一开始就开始使用它,也许您会节省一些时间 - 但在这一点上,我认为没有必要。除非某些东西不起作用,或者您认为您当前的代码难以理解,否则我会坚持使用您所拥有的。

【讨论】:

【参考方案9】:

正如您所说,基本区别是从BackgroundWorker 生成 GUI 事件。如果线程不需要为主 GUI 线程更新显示或生成事件,那么它可以是一个简单的线程。

【讨论】:

【参考方案10】:

后台工作者是一个在单独线程中工作的类,但它提供了简单线程所没有的附加功能(如任务进度报告处理)。

如果您不需要后台工作人员提供的附加功能 - 而且您似乎不需要 - 那么线程会更合适。

【讨论】:

【参考方案11】:

让我感到困惑的是,Visual Studio 设计器只允许您使用实际上不能与服务项目一起使用的 BackgroundWorkers 和 Timers。

它为您提供了简洁的拖放控件到您的服务上,但是......甚至不要尝试部署它。不会工作。

服务: 仅使用 System.Timers.Timer System.Windows.Forms.Timer 无法工作,即使它在工具箱中可用

服务: BackgroundWorkers 在作为服务运行时将不起作用 改用 System.Threading.ThreadPools 或异步调用

【讨论】:

以上是关于BackgroundWorker 与后台线程的主要内容,如果未能解决你的问题,请参考以下文章

使用后台线程BackgroundWorker处理任务的总结

vb.net主线程使用BackgroundWorker进行后台长时间的检索操作的同时怎么做到前台画面的其他操作能够响应?

BackgroundWorker控件使用

C# BackgroundWorker 详解

C# BackgroundWorker 详解

C# BackgroundWorker 详解