async等待CPU计算与IO操作的使用?

时间:2013-08-26 08:23:40

标签: c# .net-4.5 async-await c#-5.0

我已经知道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操作),我有CalcFirstMillionsDigitsOf_PI_Async任务(在sperate线程上进行大量计算绑定操作)

似乎我在这里获得的唯一事情是:

  • 保留线程上下文
  • 异常被抛回
  • 来电者线程未被屏蔽

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

我的诊断是否正确?

3 个答案:

答案 0 :(得分:3)

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

您可以在Stephen Toub的'"Invoke the method with await"... ugh!'中找到更好的解释。

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

  • 有些方法会使用Task在ThreadPool线程上运行代码,
  • 有些人会使用一些IO完成机制。甚至还有一个特殊的ThreadPool,您可以将其用于任务with a custom TaskScheduler
  • 有些会将TaskCompletionSource包装在其他机制上,例如事件或回调。

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

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

修改

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

答案 1 :(得分:3)

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

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

调用它

答案 2 :(得分:1)

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

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