【问题标题】:Difference between the TPL & async/await (Thread handling)TPL 和 async/await 之间的区别(线程处理)
【发布时间】: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,它始终返回一个专用线程。

标签: multithreading task-parallel-library async-await c#-5.0


【解决方案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 主机中,您实际上可以得到“-线程并发”。这很甜蜜。

【讨论】:

【解决方案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 中的代码,因此在这种情况下,您的工作将在线程池线程上完成。
  • 我喜欢你的简洁回答。
猜你喜欢
  • 2020-09-16
  • 2016-07-11
  • 1970-01-01
  • 2021-09-20
  • 2015-01-06
  • 1970-01-01
  • 1970-01-01
  • 2018-02-21
  • 1970-01-01
相关资源
最近更新 更多