CPU计算与IO操作的异步等待使用

本文关键字:异步 等待 操作 计算 IO CPU | 更新日期: 2023-09-27 18:29:10

我已经知道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操作(就像我上面的例子一样),那么我得到了这些东西:

  • 调用程序线程未被阻止
  • Worker线程被释放,因为存在IO操作(IO完成端口…)(GetStringAsync使用TaskCompletionSource而不打开新线程)
  • 保留的线程上下文
  • 异常被抛出

但是,如果不是httpClient.GetStringAsync(IO操作),而是CalcFirstMillionsDigitsOf_PI_Async任务(单个线程上的繁重计算绑定操作)呢

我在这里得到的似乎只有:

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

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

我的诊断是否正确?

CPU计算与IO操作的异步等待使用

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

你可以在"用wait调用方法"中找到更好的解释。。。啊!"Stephen Toub。

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

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

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

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

编辑

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

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

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

是的,你是对的。我在你的问题中找不到任何错误的说法。我不清楚"保留线程上下文"这个术语。你的意思是"逻辑控制流"吗?如果那样的话,我同意。

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