后台线程

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

我在某个地方读到过,BackgroundWorker实际上是用于那些你不想绑定 UI 的操作,并且有一个有限的结尾——比如下载一个文件,或者处理一系列的项目。在这种情况下,只有当窗口关闭时,“ end”才是未知的。因此,是否更适合我使用一个背景线程,而不是 BackgroundWorker为此目的?

128738 次浏览

如果不是坏了-修理它直到它是... 只是开玩笑:)

但是严肃地说,BackoundWorker 可能和你已经拥有的非常相似,如果你从一开始就使用它,也许你会节省一些时间——但是在这一点上我不认为有这个必要。除非有什么不对劲,或者你觉得你现在的代码很难理解,那么我还是坚持你现在的代码。

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

之所以推荐使用 BackgroundWorker来处理那些不想占用 UI 线程的事情,是因为在进行 Win Forms 开发时,它会暴露一些很好的事件。

类似 RunWorkerCompleted的事件发出信号,表示线程完成了它需要做的事情,以及 ProgressChanged事件更新线程上的 GUI。

因此,如果您使用这些 没有,我认为使用标准线程完成您需要完成的工作没有任何坏处。

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

如果您不需要后台工作人员提供的附加特性(看起来您并不需要) ,那么使用 Thread 会更合适。

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

另外,在后台工作器的生命周期中,您将绑定一个线程池线程,这可能会引起关注,因为线程池线程的数量是有限的。我想说的是,如果你只为你的应用程序创建一次线程(并且没有使用任何后台工作者的特性) ,那么使用一个线程,而不是一个后台工作者/线程池线程。

我的一些想法..。

  1. 如果有一个任务在后台运行并需要与 UI 交互,则使用 背景工作者。将数据和方法调用封送到 UI 线程的任务通过其基于事件的模型自动处理。避免背景工作者,如果..。
    • 程序集不具有或不与 UI 直接交互,
    • 您需要该线程是前台线程,或者
    • 您需要操作线程优先级。
  2. 当需要效率时使用 线程池线程。ThreadPool 有助于避免与创建、启动和停止线程相关的开销。如果... ... ,请避免使用线程池。
    • 任务在应用程序的生存期内运行,
    • 你需要这个线程是一个前台线程,
    • 您需要操作线程优先级,或者
    • 您需要线程具有一个固定的标识(中止、挂起、发现)。
  3. 对于长时间运行的任务和需要正式线程模型提供的特性时,使用 线头类,例如,在前台线程和后台线程之间进行选择,调整线程优先级,对线程执行进行细粒度控制等。

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

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

通过 ThreadPool执行的任务不能轻易取消(包括 ThreadPool)。QueueUserWorkItem和委托异步执行)。因此,尽管它避免了线程自旋的开销,如果您需要取消,可以使用 BackgroundWorker或者(更可能是在 UI 之外)自旋线程并保留对它的引用,以便您可以调用 Abort()

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

我仍然不能评论其他人的帖子,所以请原谅我一时的跛脚,使用一个答案的地址皮尔斯7

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

你知道,有时不管你使用的是 Windows 窗体、 WPF 还是其他什么技术,使用 BackoundWorker 都会更容易。这些家伙的妙处在于,您可以获得线程,而不必过分担心线程的执行位置,这对于简单的任务非常有用。

在使用 BackgroundWorker之前,首先考虑如果你想取消一个线程(关闭应用程序,用户取消) ,然后你需要决定你的线程是否应该检查取消或者它应该被推到执行本身。

BackgroundWorker.CancelAsync()会将 CancellationPending设置为 true,但不会做任何更多的事情,然后是线程负责不断地检查这一点,还要记住,在这种方法中,您可能会遇到一个竞争条件,您的用户取消了,但线程在测试 CancellationPending之前已经完成。

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

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

.NET 框架下的并行编程 托管线程最佳实践

我想指出一个还没有提到的 BackoundWorker 类的行为。可以通过设置 Thread.IsBack 属性在后台运行普通的 Thread。

后台线程与前台线程相同,只是后台线程不阻止进程终止

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

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();
}

如果将 IsContext 属性设置为 true 并关闭窗口,则应用程序将正常终止。

但是,如果默认情况下将 IsContext 属性设置为 false 并关闭该窗口,那么只有该窗口将消失,但进程仍将继续运行。

BackoundWorker 类利用在后台运行的 Thread。

让我感到困惑的是,可视化工作室设计师只允许您使用 BackoundWorkers 和 Timers,而这些工作室实际上并不与服务项目一起工作。

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

服务范围: 只使用系统。定时器。定时器 System.Windows.Forms.Timer 即使在工具箱中也无法工作

服务范围: 当它作为服务运行时,Workers 将无法工作 改为使用 System.Thread.ThreadPools 或异步调用

如果在线程内部生成异常,将导致 WinForms 应用程序关闭。如果 BackoundWorker 在其中生成异常,则不会影响应用程序的运行