为何async-await使用时线程表现存在差异?求详细解析
异步线程调度差异原理详解
你困惑的核心是两个异步程序中,await之后的代码执行线程不同,本质是任务完成时机与原线程状态的交互,结合.NET异步调度规则拆解如下:
核心规则前提
控制台程序默认没有SynchronizationContext(WinForm/WPF这类UI程序才有),所以await完成后:
- 如果原线程还在忙碌,任务剩余逻辑会由线程池线程执行;
- 如果
await时任务已经完成,直接在当前线程继续执行; - 如果
await时任务未完成,原线程会释放回线程池,任务完成后由线程池任意线程接手后续代码。
第一个程序分析
代码
Console.WriteLine("print1 Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); Task q = FetchAndShowHeaders("http://www.fultonbank.com"); Console.WriteLine("print2 Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); for (int i = 0; i < 500_000_0; i++) { for (int ii = 0; ii < 500; ii++) { if (i % 500_000 == 0) Console.Write("."); } } Console.WriteLine("\nprint5 Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); await q; Console.WriteLine("print6 Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); return; async Task FetchAndShowHeaders(string url) { using var w = new HttpClient(); var req = new HttpRequestMessage(HttpMethod.Head, url); Console.WriteLine("\nprint3_method_inside Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); var response = await w.SendAsync(req, HttpCompletionOption.ResponseHeadersRead); Console.WriteLine(System.Text.Json.JsonSerializer.Serialize( response.Headers.ToString())); Console.WriteLine("\nprint4_method_inside Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); }
输出
print1 Thread Name = 1 print3_method_inside Thread Name = 1 print2 Thread Name = 1 ...................................... ................................."Date: Sat, 10 Feb 2024 21:08:01 GMT\r\nConnection: keep-alive\r\nSet-Cookie: AWSALB=yoEkz4GX4D6NZxccaPs1OQfQGu0yNRocZplmI6TE1pU1GB2BNwAcQ01\u002Bc; Expires=Sat, 17 Feb 2024 21:08:01 GMT; Path=/, AWSALBCORS=yoEkz4GX4D6NZIci84X7t\u 002BNwAcQ01\u002Bc; Expires=Sat, 17 Feb 2024 21:08:01 GMT; Path=/; SameSite=None; Secure, ASP.NET_SessionId=al3ekazz02q0up1oja4i0ify; path=/; HttpOnly; SameSite=Lax,\r\nX-Frame-Options: DENY\r\nStrict-Transport-Security: max-age=31536000;includeSubDomains\r\nX-Content-Type-Options: nosniff\r\nX-XSS-Protection: 1; mode=block\r\n" print4_method_inside Thread Name = 10 ................................................................. ............................................................................ print5 Thread Name = 1 print6 Thread Name = 1
执行流程
- 线程1启动,执行
print1,调用FetchAndShowHeaders到await w.SendAsync时,线程1返回继续执行后续的嵌套循环(这个循环极度耗时,线程1一直被占用)。 SendAsync完成后,线程池线程10接手执行FetchAndShowHeaders剩余代码(print4),任务q标记为完成。- 线程1还在跑循环,直到执行到
await q时,任务q已经完成,所以直接在线程1上继续执行print6,无需切换线程。
第二个程序分析
代码
Console.WriteLine("print1 Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); Task q = FetchAndShowHeaders("http://www.fultonbank.com"); Console.WriteLine("print2 Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); for (int i = 0; i < 11; i++) { for (int ii = 0; ii < 11; ii++) { Console.Write("."); } } Console.WriteLine("\nprint5 Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); await q; Console.WriteLine("print6 Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); return; async Task FetchAndShowHeaders(string url) { Console.WriteLine("\nprint3_method_inside Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); await Task.Delay(1000); Console.WriteLine("\nprint4_method_inside Thread Name = {0}", Thread.CurrentThread.ManagedThreadId); }
输出
print1 Thread Name = 5 print3_method_inside Thread Name = 5 print2 Thread Name = 5 ......................................................................................................................... print5 Thread Name = 5 print4_method_inside Thread Name = 9 print6 Thread Name = 9
执行流程
- 线程5启动,执行
print1,调用FetchAndShowHeaders到await Task.Delay(1000)时,线程5返回执行后续循环(这个循环很快,121个点,线程5很快执行完到await q)。 - 此时
Task.Delay还没完成,线程5在await q时会被释放回线程池,等待任务完成。 - 1秒后
Task.Delay完成,线程池线程9接手执行FetchAndShowHeaders剩余代码(print4),任务q标记为完成。 - 因为原线程5已经空闲回到线程池,没有线程在等待
await q的结果,所以线程9直接继续执行await之后的print6。
关键差异总结
- 第一个程序:
await q执行时任务已完成,直接复用当前忙碌的原线程(线程1)执行后续代码。 - 第二个程序:
await q执行时任务未完成,原线程被释放,任务完成后由线程池线程(线程9)接手后续执行。
内容的提问来源于stack exchange,提问作者Hax
相关产品推荐
相关产品推荐

