4

我已经知道async-await保留线程上下文,还处理异常转发等(这很有帮助)。

但请考虑以下示例:

/*1*/   public async Task<int> ExampleMethodAsync()
/*2*/   {
/*3*/       var httpClient = new HttpClient(); 
/*4*/      
/*5*/       //start async task...
/*6*/       Task<string> contentsTask = httpClient.GetStringAsync("http://msdn.microsoft.com");
/*7*/   
/*8*/       //wait and return...  
/*9*/       string contents = await contentsTask;
/*10*/   
/*11*/       //get the length...
/*12*/       int exampleInt = contents.Length;
/*13*/       
/*14*/       //return the length... 
/*15*/       return exampleInt;
/*16*/   }

如果async方法 ( httpClient.GetStringAsync) 是一个 IO 操作(就像我上面的示例)那么 - 我得到了这些东西:

  • 调用者线程未被阻塞
  • 工作线程被释放,因为有一个IO操作(IO 完成端口...)(GetStringAsync 使用TaskCompletionSource而不是打开一个新线程)
  • 保留的线程上下文
  • 异常被抛出

但是,如果不是httpClient.GetStringAsync(IO operation) ,我有一个Task of CalcFirstMillionsDigitsOf_PI_Async(heavy compute bound operation on a sperate thread)

似乎我在这里获得的唯一东西是:

  • 保留的线程上下文
  • 异常被抛出
  • 调用者线程未被阻塞

但我还有另一个线程(并行线程)执行操作。并且cpu在主线程和操作之间切换。

我的诊断是否正确?

4

3 回答 3

3

实际上,在这两种情况下,您只能获得第二组优势。await不会启动任何异步执行,它只是编译器的一个关键字,用于生成用于处理完成、上下文等的代码。

您可以在“使用 await 调用方法”中找到对此的更好解释……呃!’斯蒂芬·图布。

由异步方法本身决定如何实现异步执行:

  • 一些方法将使用 Task 在 ThreadPool 线程上运行它们的代码,
  • 有些会使用一些 IO 完成机制。甚至还有一个特殊的 ThreadPool,您可以将其与带有自定义 TaskScheduler的任务一起使用
  • 有些会将 TaskCompletionSource 包装在另一种机制上,例如事件或回调。

在每种情况下,释放线程的都是特定的实现(如果使用了线程)。TaskScheduler 会在 Task 完成执行时自动释放线程,因此无论如何您都可以在案例 #1 和 #2 中获得此功能。

第 3 种情况下的回调会发生什么,取决于回调是如何进行的。大多数情况下,回调是在某个外部库管理的线程上进行的。在这种情况下,您必须快速处理回调并返回以允许库重用该方法。

编辑

使用反编译器,可以看到它GetStringAsync使用了第三个选项:它创建了一个 TaskCompletionSource,当操作完成时会发出信号。执行操作被委托给 HttpMessageHandler。

于 2013-08-26T11:39:36.633 回答
3

您的分析是正确的,尽管您第二部分的措辞听起来像是async在为您创建一个工作线程,但事实并非如此。

在库代码中,您实际上希望保持同步方法同步。如果您想异步使用同步方法(例如,从 UI 线程),请使用await Task.Run(..)

于 2013-08-26T12:07:08.513 回答
1

是的,你是对的。我在你的问题中找不到任何错误的陈述。我不清楚“保留的线程上下文”一词。你的意思是“逻辑控制流”吗?在那种情况下,我会同意。

关于 CPU 绑定示例:您通常不会这样做,因为启动基于 CPU 的任务并等待它会增加开销并降低吞吐量。但是,如果您需要解除对调用者的阻止(例如,在 WinForms 或 WFP 项目的情况下),这可能是有效的。

于 2013-08-26T12:03:42.470 回答