跳到主要内容

2.8 async 与 await

async / await 是 .NET 全栈开发中处理 IO 的基础能力。Web API、数据库、HTTP 调用、文件读写都应该优先使用异步方式,避免线程被长时间阻塞。

学习目标

  • 理解异步不是多线程,而是释放等待 IO 的线程。
  • 能正确返回 TaskTask<T>ValueTask<T>
  • 能把 CancellationToken 传递到数据库和 HTTP 调用。
  • 能识别常见死锁、吞异常和并发误用。

应用场景

  • ASP.NET Core API 调用数据库。
  • 后端调用第三方 HTTP 服务。
  • 批量处理文件、消息队列或后台任务。
  • 前端提交后等待后端返回结果。

核心概念

概念说明
Task表示一个还未完成或已经完成的异步操作
await异步等待结果,不阻塞当前线程
CancellationToken取消请求或超时操作的协作信号
Task.WhenAll并发等待多个互不依赖的异步操作

案例:异步查询任务

app.MapGet("/todos/{id:int}", async (
int id,
AppDbContext dbContext,
CancellationToken cancellationToken) =>
{
var todo = await dbContext.Todos
.AsNoTracking()
.Where(item => item.Id == id)
.Select(item => new TodoDto(item.Id, item.Title, item.IsCompleted))
.SingleOrDefaultAsync(cancellationToken);

return todo is null ? Results.NotFound() : Results.Ok(todo);
});

示例:并发调用互不依赖服务

public async Task<DashboardDto> GetDashboardAsync(
int userId,
CancellationToken cancellationToken)
{
var todosTask = todoService.GetTodayTodosAsync(userId, cancellationToken);
var profileTask = userService.GetProfileAsync(userId, cancellationToken);

await Task.WhenAll(todosTask, profileTask);

return new DashboardDto(
await todosTask,
await profileTask);
}

只有两个调用互不依赖时才适合并发;如果第二个调用需要第一个调用结果,就应顺序 await

重点难点

  • 不要用 .Result.Wait() 阻塞异步代码。
  • Web API 中不要忽略 CancellationToken,客户端断开时应尽快停止无意义工作。
  • async void 只适合少数事件处理,业务代码应返回 Task
  • 并发不是越多越好,数据库和第三方服务都有连接和速率限制。

常见误区

误区推荐做法
认为 await 会新建线程理解它是异步等待,不等于开线程
为了“更快”到处 Task.RunIO 操作直接使用框架异步 API
捕获异常后只写日志不抛出明确返回失败结果或继续抛给统一错误处理

练习

  • 把同步 EF Core 查询改为异步查询。
  • 给一个 API 传入 CancellationToken,并传递到所有异步调用。
  • 使用 Task.WhenAll 并发加载用户资料和任务统计。