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 的方法都可以是awaited - 它不必是async 方法。所以Task.Delay 和 I/O 绑定操作只需使用TaskCompletionSource 来创建和完成任务——线程池上唯一要做的就是事件发生时的实际任务完成(超时、I/O 完成等) )。

我想我对 FromCurrentSynchronizationContext 的理解也总是有点模糊。我一直认为它本质上是 UI 线程。

我在SynchronizationContext 上写了an article。大多数时候,SynchronizationContext.Current

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

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

注意默认的Task awaiter 会在当前SynchronizationContext 上调度async 方法的剩余部分如果它不为空;否则它将继续当前的TaskScheduler。这在今天并不那么重要,但在不久的将来它将是一个重要的区别。

我在博客上写了自己的async/await intro,Stephen Toub 最近发布了一个出色的async/await FAQ。

关于“并发”与“多线程”,请参阅this related SO question。我想说async 启用并发,这可能是也可能不是多线程的。使用await Task.WhenAllawait Task.WhenAny 做并发处理很容易,除非你显式使用线程池(例如Task.RunConfigureAwait(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 会创建一个新线程吗?

async,await与task.wait()或task.Result的区别

promise与async和await的区别

js异步回调Async/Await与Promise区别 新学习使用Async/Await

Async await 异步编程说明