TPL与异步/等待(线程处理)之间的区别


72

试图了解TPL&async/await与线程创建之间的区别。

我相信TPL(TaskFactory.StartNew)的工作方式ThreadPool.QueueUserWorkItem与之类似,因为它使线程池中的线程上的工作排队。当然,除非您使用TaskCreationOptions.LongRunning它创建一个新线程。

我认为async/await本质上将类似地工作:

TPL:

Factory.StartNew( () => DoSomeAsyncWork() )
.ContinueWith( 
    (antecedent) => {
        DoSomeWorkAfter(); 
    },TaskScheduler.FromCurrentSynchronizationContext());

Async/ Await

await DoSomeAsyncWork();  
DoSomeWorkAfter();

将是相同的。从我一直在阅读的内容来看,似乎async/ await“有时”创建了一个新线程。那么,何时创建新线程,何时不创建新线程?如果您正在处理IO完成端口,我可以看到它不必创建新线程,但否则我认为必须这样做。我想我对FromCurrentSynchronizationContext永远的理解也有些模糊。从本质上讲,我始终认为它是UI线程。


4
实际上,TaskCreationOptions.LongRunning不保证“新线程”。对于MSDN,“ LongRunning”选项仅向调度程序提供提示;它不保证有专用线程。 我发现很难。
eduncan911

@ eduncan911尽管您对文档的说法是正确的,但我前不久查阅了TPL源代码,并且我很确定实际上在TaskCreationOptions.LongRunning指定时始终会创建一个新的专用线程。
Zaid Masud 2013年

@ZaidMasud:您可能想再看看。我知道它正在合并线程,因为Thread.CurrentThread.IsThreadPoolThread对于几百毫秒的短时间运行线程返回true。更不用说我正在使用的ThreadStatic变量渗入多个线程,从而导致各种各样的问题。我不得不强迫我的代码以老式的方式重新创建多个Thread(),以保证有专用线程。换句话说,我无法将TaskFactory用于专用线程。(可选)您可以实现自己的TaskScheduler始终返回专用线程的方法。
eduncan911

Answers:


85

我相信TPL(TaskFactory.Startnew)的工作方式类似于ThreadPool.QueueUserWorkItem,因为它使线程池中的线程上的工作排队。

差不多了

从我一直在阅读的内容来看,似乎只有异步/等待才“有时”创建一个新线程。

实际上,它从未如此。如果要使用多线程,则必须自己实现。有一种新Task.Run方法只是该方法的简写Task.Factory.StartNew,它可能是在线程池上启动任务的最常用方法。

如果您正在处理IO完成端口,我可以看到它不必创建新线程,否则我认为它必须这样做。

答对了。因此,类似的方法Stream.ReadAsync实际上会Task在IOCP周围创建包装器(如果StreamIOCP)。

您还可以创建一些非I / O,非CPU的“任务”。一个简单的例子是Task.Delay,它返回在一段时间后完成的任务。

关于async/的最酷的事情await是,您可以将一些工作排​​队到线程池中(例如Task.Run),执行一些I / O绑定操作(例如Stream.ReadAsync),然后执行其他一些操作(例如Task.Delay)...所有任务!可以等待它们,也可以结合使用,例如Task.WhenAll

任何返回的方法Task都可以await编辑-不必一定是async方法。因此,Task.Delay与I / O绑定的操作仅用于TaskCompletionSource创建和完成任务-在线程池上唯一要做的事情是事件发生时的实际任务完成(超时,I / O完成等)。

我想我对FromCurrentSynchronizationContext的理解也总是有点模糊。从本质上讲,我始终认为它是UI线程。

我写了一篇文章SynchronizationContext。大多数时候SynchronizationContext.Current

  • 如果当前线程是UI线程,则是UI上下文。
  • 如果当前线程正在为ASP.NET请求提供服务,则为ASP.NET请求上下文。
  • 否则为线程池上下文。

任何线程都可以设置自己的线程SynchronizationContext,因此上述规则也有例外。

请注意,如果默认的Task等待者不为null它将async在当前方法上调度方法的其余部分;否则它会在当前SynchronizationContext TaskScheduler。今天这并不是很重要,但是在不久的将来,这将是一个重要的区别。

我在自己的博客上写了自己的async/await介绍,而Stephen Toub最近发布了一个出色的async/await常见问题解答

关于“并发”与“多线程”,请参阅此相关的SO问题。我会说async启用并发,它可能是多线程的,也可能不是。使用await Task.WhenAllawait Task.WhenAny执行并发处理都很容易,除非您显式使用线程池(例如Task.RunConfigureAwait(false)),否则您可以同时进行多个并发操作(例如,多个I / O或其他类型,例如Delay)-而且它们不需要线程。在这种情况下,我使用术语“单线程并发”,尽管在ASP.NET主机中,实际上可以得到“线程并发”。真是太好了


2
好答案。我还将建议infoq.com/articles/Async-API-Design和出色的演示文稿:channel9.msdn.com/Events/TechEd/Europe/2013/DEV-B318
菲利普

第一个链接已死。
费利佩·德维萨

“异步/等待常见问题解答”链接已失效
Artemious

对。微软移动了很多东西,几乎断开了所有到其内容的链接。此答案接受PR。
斯蒂芬·克莱里

9

异步/等待基本上简化了ContinueWith方法(Continuation Passing Style中的Continuations)。

它没有引入并发性-您仍然必须自己做(或使用框架方法的Async版本。)

因此,C#5版本将是:

await Task.Run( () => DoSomeAsyncWork() );
DoSomeWorkAfter();

所以在上面的示例中,它在哪里运行DoSomeAsyncWork(异步/等待版本)?如果它在UI线程上运行,它不会被阻塞吗?
coding4fun

1
如果DoSomeWorkAsync()返回void或无法等待的内容,则您的等待示例将不会编译。从第一个示例中,我假设它是要在其他线程上运行的顺序方法。如果您将其更改为返回Task,而没有引入并发性,那么它会阻塞。从某种意义上说,它将按顺序执行,并且就像UI线程上的常规代码一样。await仅在该方法返回尚未完成的awaitable时产生。
Nicholas Butler

好吧,我不会说它选择运行在任何地方。您已经使用Task.Run在DoSomeAsyncWork中执行代码,因此在这种情况下,您的工作将在线程池线程上完成。
Michael Ray Lovett

我喜欢你回答的简洁。
RBT
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.