TPL 和 async/await 之间的区别(线程处理)
Posted
技术标签:
【中文标题】TPL 和 async/await 之间的区别(线程处理)【英文标题】:Difference between the TPL & async/await (Thread handling) 【发布时间】:2012-05-04 08:05:19 【问题描述】:在线程创建方面尝试了解 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 线程。
【问题讨论】:
实际上,TaskCreationOptions.LongRunning 并不能保证“新线程”。根据 MSDN,“LongRunning”选项仅向调度程序提供提示;它不保证有一个专用线程。我发现了这个困难的方式。 @eduncan911 尽管您对文档的看法是正确的,但我不久前查阅了 TPL 源代码,我很确定实际上在TaskCreationOptions.LongRunning
时总是会创建一个新的专用线程已指定。
@ZaidMasud:你可能想再看看。我知道它正在合并线程,因为 Thread.CurrentThread.IsThreadPoolThread
对于几百毫秒的短时间运行线程返回 true。更不用说我使用的 ThreadStatic 变量渗入多个线程,导致各种破坏。我不得不强制我的代码新建多个 Thread(),这是老式的方式,以保证专用线程。换句话说,我不能将 TaskFactory 用于专用线程。或者,您可以实现自己的 TaskScheduler
,它始终返回一个专用线程。
【参考方案1】:
我相信 TPL (TaskFactory.Startnew) 的工作方式与 ThreadPool.QueueUserWorkItem 类似,因为它将线程池中线程上的工作排队。
Pretty much.
从我一直在阅读的内容来看,async/await 似乎只是“有时”会创建一个新线程。
实际上,它从来没有。如果你想要多线程,你必须自己实现它。有一个新的Task.Run
方法,它只是Task.Factory.StartNew
的简写,它可能是在线程池上启动任务的最常用方法。
如果您正在处理 IO 完成端口,我可以看到它不必创建新线程,否则我认为它必须这样做。
宾果游戏。所以像 Stream.ReadAsync
这样的方法实际上会在 IOCP 周围创建一个 Task
包装器(如果 Stream
有一个 IOCP)。
您还可以创建一些非 I/O、非 CPU 的“任务”。一个简单的例子是Task.Delay
,它返回一个在一段时间后完成的任务。
async
/await
很酷的一点是,您可以将一些工作排队到线程池(例如,Task.Run
),执行一些 I/O 绑定操作(例如,Stream.ReadAsync
),以及做一些其他操作(例如,Task.Delay
)......它们都是任务!它们可以被等待或组合使用,例如Task.WhenAll
。
任何返回Task
的方法都可以是await
ed - 它不必是async
方法。所以Task.Delay
和 I/O 绑定操作只需使用TaskCompletionSource
来创建和完成任务——线程池上唯一要做的就是事件发生时的实际任务完成(超时、I/O 完成等) )。
我想我对 FromCurrentSynchronizationContext 的理解也总是有点模糊。我一直认为它本质上是 UI 线程。
我在SynchronizationContext
上写了an article。大多数时候,SynchronizationContext.Current
:
任何线程都可以设置自己的SynchronizationContext
,所以上面的规则也有例外。
注意默认的Task
awaiter 会在当前SynchronizationContext
上调度async
方法的剩余部分如果它不为空;否则它将继续当前的TaskScheduler
。这在今天并不那么重要,但在不久的将来它将是一个重要的区别。
我在博客上写了自己的async
/await
intro,Stephen Toub 最近发布了一个出色的async
/await
FAQ。
关于“并发”与“多线程”,请参阅this related SO question。我想说async
启用并发,这可能是也可能不是多线程的。使用await Task.WhenAll
或await Task.WhenAny
做并发处理很容易,除非你显式使用线程池(例如Task.Run
或ConfigureAwait(false)
),那么你可以同时进行多个并发操作(例如,多个 I/O 或其他类型,如 Delay
) - 它们不需要线程。对于这种情况,我使用术语“单线程并发”,尽管在 ASP.NET 主机中,您实际上可以得到“零-线程并发”。这很甜蜜。
【讨论】:
不错的答案。我还推荐infoq.com/articles/Async-API-Design 和这个出色的演示:channel9.msdn.com/Events/TechEd/Europe/2013/DEV-B318。 第一个链接失效了。 “async/await FAQ”链接失效 是的。微软移动了一堆东西,并破坏了几乎所有指向其内容的链接。此答案接受 PR。【参考方案2】:async / await 基本上简化了ContinueWith
方法(在Continuation Passing Style 中继续)
它没有引入并发——你仍然需要自己做(或者使用框架方法的异步版本。)
因此,C# 5 版本将是:
await Task.Run( () => DoSomeAsyncWork() );
DoSomeWorkAfter();
【讨论】:
那么在我上面的示例中,它在哪里运行 DoSomeAsyncWork(异步/等待版本)?如果它在 UI 线程上运行,它如何不阻塞? 如果DoSomeWorkAsync()
返回 void 或不可等待的东西,您的 await 示例将无法编译。从您的第一个示例中,我假设它是您希望在不同线程上运行的顺序方法。如果您将其更改为返回Task
,而不引入并发,那么是的,它会阻塞。从某种意义上说,它会按顺序执行,就像 UI 线程上的普通代码一样。 await
仅在方法返回尚未完成的等待时才会产生。
好吧,我不会说它会在它选择运行的任何地方运行。您已使用 Task.Run 执行 DoSomeAsyncWork 中的代码,因此在这种情况下,您的工作将在线程池线程上完成。
我喜欢你的简洁回答。以上是关于TPL 和 async/await 之间的区别(线程处理)的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin Coroutine,Android Async Task 和 Async await 的区别
async,await与task.wait()或task.Result的区别