Task.Run与ThreadPool.QueueUserWorkItem的区别

54

作为非专业人士,我正在尝试更好地了解.NET中可用的异步世界。Task.Run和ThreadPool.QueueUserWorkItem都允许在池线程上分派工作,但它们有什么区别或者如果您愿意,两者的优缺点是什么?

以下是我的优点列表。不确定是否完整或甚至正确。

ThreadPool.QueueUserWorkItem的优点:

  • 可以传递参数

Task.Run的优点:

  • 可以提供CancellationToken
  • 可以等待Task完成
  • 可以将返回值返回给调用代码

1
你可以通过 lambda 表达式中的变量捕获来向 Task.Run 传递参数。 - Scott Chamberlain
真的。我没有考虑变量捕获,因为这也可以通过ThreadPool.QueueUserWorkItem实现。 - Stefano
在 threadPool.QueueUserWorkItem 中提供 CancellationToken 的可能性 --- 存在这样的机会。 - Artem
2个回答

52

ThreadPool.QueueUserWorkItem只是一个旧的实现(在.NET 1.1中引入),与Task.Run(在.NET 4.5中引入)执行相同的工作。

Microsoft试图避免在.NET中破坏向后兼容性。 在.NET 1.1编写的内容通常可以在.NET 4.5中编译和运行,而无需进行更改。 通常情况下,破坏性变化来自于编译器的更改,而不是框架的更改,比如在C# 5及更高版本中,在lambda内部使用foreach声明的变量会表现出不同的行为

当您拥有Task.Run时,没有真正的理由使用ThreadPool.QueueUserWorkItem

最后要说的是:具有讽刺意味的是,事实上,通过HostingEnvironment.QueueBackgroundWorkItem(...),一切都已经回到了起点。 它允许您在ASP.NET环境中在后台线程上运行某些操作,并使Web服务器在长时间不活动期间通知后台工作关于AppDomain关闭的情况。


1
如果GC抓取返回的任务并对其调用dispose,那么后台线程会停止吗?(假设我像QUWI一样使用Task.Run而不接受或跟踪返回的Task<>对象)。如果是这样的话,那将是一个很大的区别,对吧,因为有时Task可能会被终止,而QUWI没有这样的风险,是吗? - pbristow
5
不,它不会。使用Task.Run返回的任务对象有一个内部静态根,因此在线程处于运行状态时,该对象不符合垃圾回收的条件。 - Scott Chamberlain
15
我对这种说法“当你有Task.Run时,没有真正的理由使用ThreadPool.QueueUserWorkItem”持不同意见。至少可以说这种说法是不完整的。正如其他评论者(zh chen)所提到的:如果未被观察,Task.Run将吞噬错误,而ThreadPool.QueueUserWorkItem将作为未处理的异常传播。在我看来,这总是比让错误保持未被检测状态更好。在某些情况下,使用任务更好且更易编程,但盲目地用“新”的模式替换“旧”的模式而不理解所有后果绝不是一个好主意。 - Serge Pavlov

34

我最近意识到 ThreadPool.QueueUserWorkItemTask.Run 之间的一个区别是它们处理异常的方式。

如果在 ThreadPool.QueueUserWorkItem 中发生未处理的异常并且没有被全局异常处理程序处理,它将导致父线程崩溃。另一方面,从 Task.Run 线程中出现的未处理异常直到你使用 awaitTask.Wait 才会传播。


1
看起来这个行为也适用于其他线程函数:我有一个 WPF 应用程序,它启动一个线程在后台检查是否存在新版本。如果没有网络,它会在 .net > 4.5 上静默失败,但在运行 .net 2.x 的 Windows XP 上会导致应用程序崩溃。 - unkreativ
在ASP.NET中,如果父线程可能已经返回到线程池中,那该怎么办? - xr280xr

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