BackgroundWorker与后台线程

时间:2009-10-01 22:25:52

标签: .net winforms multithreading backgroundworker

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

我在某处读到BackgroundWorker实际上是用于那些你不想将UI绑定并具有有限结束的操作 - 比如下载文件或处理一系列项目。在这种情况下,“结束”是未知的,并且仅在窗口关闭时。因此,为此目的使用后台线程而不是BackgroundWorker会更合适吗?

11 个答案:

答案 0 :(得分:350)

我的一些想法......

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

答案 1 :(得分:83)

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

为不希望绑定UI线程的事情推荐BackgroundWorker的原因是因为它在进行Win Forms开发时会暴露一些不错的事件。

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

因此,如果你没有使用这些,我认为使用标准线程不会对你需要做什么造成任何伤害。

答案 2 :(得分:12)

Matt Davis说的话几乎有以下几点:

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

通过ThreadPool执行的任务无法轻松取消(包括ThreadPoolQueueUserWorkItem和委托执行异步)。因此,虽然它避免了线程旋转的开销,但是如果你需要取消,可以使用BackgroundWorker或(更可能在UI之外)启动一个线程并保持对它的引用,这样你就可以调用Abort()

答案 3 :(得分:10)

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

答案 4 :(得分:8)

您知道,无论您使用的是Windows窗体,WPF还是其他任何技术,有时使用BackgroundWorker都会更容易。关于这些人的简洁部分是你得到线程而不必过多担心线程执行的位置,这对于简单的任务来说非常有用。

在使用BackgroundWorker之前,如果您想要取消线程(关闭应用程序,用户取消),那么您需要确定您的线程是否应检查取消或是否应该在执行本身上进行检查。

BackgroundWorker.CancelAsync()会将CancellationPending设置为true,但不会再执行任何操作,因此线程有责任不断检查此项,请记住您最终可能会这种方法中的竞争条件,用户取消了,但在测试CancellationPending之前完成了线程。

另一方面,

Thread.Abort()将在线程执行中抛出一个异常,强制取消该线程,如果在执行过程中突然引发了这个异常,你必须小心可能会有什么危险。

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

Parallel Programming in the .NET Framework Managed Threading Best Practices

答案 5 :(得分:4)

在我熟悉.NET之前,我知道如何使用线程,因此在我开始使用BackgroundWorkers时需要一些时间来习惯。 Matt Davis总结了这种差异,并且非常出色,但我想补充说,理解代码正在做什么更难以理解,这可能会使调试变得更加困难。考虑创建和关闭线程IMO比考虑为线程池提供工作更容易。

我仍然无法评论其他人的帖子,所以原谅我在使用答案解决码头时的短暂跛脚7

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

答案 6 :(得分:2)

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

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

答案 7 :(得分:2)

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

答案 8 :(得分:1)

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

如果你不需要后台工作者提供的附加功能 - 而且你似乎没有 - 那么一个Thread会更合适。

答案 9 :(得分:1)

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

  

后台线程与前台线程相同,但后台线程不会阻止进程终止。 [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 = false; // <--- This will make the thread run in background
    thread.IsBackground = true; // <--- This will delay program termination

    thread.Start();
}

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

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

BackgroundWorker类使用在后台运行的Thread。

答案 10 :(得分:-1)

令我感到困惑的是,视觉工作室设计师只允许您使用不实际使用服务项目的BackgroundWorkers和Timers。

它为您提供整洁的拖放控件,但是......甚至不尝试部署它。不会工作。

服务: 仅使用System.Timers.Timer System.Windows.Forms.Timer即使在工具箱中可用也不会工作

服务: BackgroundWorkers在作为服务运行时将无法工作 请改用System.Threading.ThreadPools或Async calls