You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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. 线程1启动,执行print1,调用FetchAndShowHeaders到await w.SendAsync时,线程1返回继续执行后续的嵌套循环(这个循环极度耗时,线程1一直被占用)。
  2. SendAsync完成后,线程池线程10接手执行FetchAndShowHeaders剩余代码(print4),任务q标记为完成。
  3. 线程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

执行流程

  1. 线程5启动,执行print1,调用FetchAndShowHeaders到await Task.Delay(1000)时,线程5返回执行后续循环(这个循环很快,121个点,线程5很快执行完到await q)。
  2. 此时Task.Delay还没完成,线程5在await q时会被释放回线程池,等待任务完成。
  3. 1秒后Task.Delay完成,线程池线程9接手执行FetchAndShowHeaders剩余代码(print4),任务q标记为完成。
  4. 因为原线程5已经空闲回到线程池,没有线程在等待await q的结果,所以线程9直接继续执行await之后的print6。

关键差异总结

  • 第一个程序:await q执行时任务已完成,直接复用当前忙碌的原线程(线程1)执行后续代码。
  • 第二个程序:await q执行时任务未完成,原线程被释放,任务完成后由线程池线程(线程9)接手后续执行。

内容的提问来源于stack exchange,提问作者Hax

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 12:47:09