×

C#异步编程:async / await 的执行模型

独孤求败 独孤求败 发表于2026-05-09 16:13:17 浏览38 评论0

抢沙发发表评论

很多初学者有一个误解:

await Task.Delay(2000);

好像就是:

Thread.Sleep(2000);

但实际上两者完全不同。

一、同步代码的执行模型

先看同步代码:

static void Foo()
{
    Console.WriteLine("A");
    Thread.Sleep(2000);
    Console.WriteLine("B");
}

执行流程:

线程进入 Foo
 ↓
执行 A
 ↓
Sleep 2 秒(线程被阻塞)
 ↓
执行 B
 ↓
返回

特点:

  • 线程一直在这个方法里
  • 即使等待期间也不能干别的事情

二、async / await 的代码执行过程

现在看异步代码:

async Task FooAsync()
{
    Console.WriteLine("A");
    await Task.Delay(2000);
    Console.WriteLine("B");
}

表面看起来和同步几乎一样。但执行流程完全不同。

当执行到:

await Task.Delay(2000);

运行时发生三件事:

1. 创建 Task

Task t = Task.Delay(2000);

这个 Task 表示:2 秒后完成

2. 检查 Task 是否完成

运行时会检查:

if (!t.IsCompleted)

因为还没完成,所以继续下面步骤。

3. 挂起方法

编译器会把方法拆成 状态机

此时:

  • 当前方法暂停
  • 记录执行位置
  • 注册 continuation

也就是:

t.ContinueWith(_ => ResumeMethod());

当 Task 完成时,恢复方法执行。然后:当前线程立即返回。

4. 两秒后发生什么?

Task.Delay 完成时:运行时会触发 continuation。于是:

线程池线程
 ↓
恢复 FooAsync
 ↓
执行 Console.WriteLine("B")
 ↓
Task 完成

核心对比

特性
Thread.Sleep
await Task.Delay
是否占线程
等待期间线程
被阻塞
可以去干别的
可扩展性

这就是为什么:async / await 可以支持高并发。

四、一个非常关键的认知

很多人误以为:

await 会创建新线程

这是 错误的。await 的真实行为是:

  1. 如果 Task 未完成
  2. 挂起当前方法
  3. 注册 continuation
  4. 线程立即返回

所以:await 的核心不是线程,而是 暂停与恢复执行流

一个简单的实验,运行下面代码:

static async Task Test()
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

    await Task.Delay(1000);

    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

在 Console 程序 中很可能看到:

1
5

说明:

  • 方法恢复时可能是另一个线程

这再次证明:async 方法并不绑定某个线程。

我们需要记住一句话:**await 并不会等待,它只是挂起方法。**真正等待的是:Task 本身。

五、真正的场景:服务器处理请求

假设一个 Web 服务器。

每个请求都要调用数据库:

var data = await db.GetDataAsync();

数据库响应需要 1 秒

现在同时来了 1000 个请求


情况 A:同步代码

var data = db.GetData(); // 阻塞

执行过程:

线程1 等数据库
线程2 等数据库
线程3 等数据库
...
线程200 等数据库

假设服务器最多 200 个线程

那么:

  • 前 200 个请求占满线程
  • 剩下 800 个请求只能排队

吞吐量被线程数限制。


情况 B:async/await

var data = await db.GetDataAsync();

执行过程:

线程1 发起数据库请求
线程1 释放
线程2 发起数据库请求
线程2 释放
线程3 发起数据库请求
线程3 释放

所有请求都可以:

  • 发起数据库 I/O
  • 然后 立即释放线程

线程可以继续处理新的请求。

数据库返回时:

  • 线程池拿一个线程
  • 执行 continuation

关键差别

同步模型:

线程被占住等待 I/O

异步模型:

线程只用于发起请求和处理结果
等待期间不占线程

所以:

请求数
同步线程数
异步线程数
1000
1000线程
可能只需要几十个线程

这就是:

async/await 的可扩展性。


“线程去干别的事”到底是什么

不是同一个方法干别的事。

而是:

线程返回线程池,然后被用来执行其他 Task。

例如:

线程1 执行请求A
await DB
线程1 返回线程池

线程1 执行请求B
await HTTP
线程1 返回线程池

线程1 执行请求C
await File

一个线程在一秒内可以处理很多“发起请求”的操作。


群贤毕至

访客