2026-09-15 · C# 基础 · 阅读约 7 分钟

C# 异步 async/await 学习笔记整理

异步编程是 C# 学习绕不开的一道坎。最开始接触 async/await 的时候,只知道"加个 await 就不卡了",但真要自己写异步方法时,心里完全没底。最近把这一块重新系统学了一遍,把真正理解的几个要点整理成这篇笔记,方便以后回来查。

一、async/await 的基本写法

异步方法的标准写法是在返回类型前加 async,方法内部用 await 等待异步操作完成。注意:除非是事件处理器,否则不要返回 void,应该返回 Task 或 Task<T>。这样调用方才能 await,异常也才能被正确捕获。

public async Task<string> GetHtmlAsync(string url)
{
    using var http = new HttpClient();
    // await 等待网络请求,当前线程不会被阻塞
    string html = await http.GetStringAsync(url);
    return html;
}

二、Task 与 ValueTask 的区别

学习时一个容易混淆的点是 Task 和 ValueTask。Task<T> 是引用类型,分配在堆上,适合大多数场景;ValueTask<T> 是值类型,在同步已经有结果时可以避免堆分配,性能更好,但用法有一些限制。自己的理解是:库作者在可能同步完成的 API 上用 ValueTask,普通业务代码直接用 Task 就够了,不要为了"看起来高级"到处用 ValueTask。

三、ConfigureAwait(false) 什么时候用

在类库中写异步方法时,通常要加 ConfigureAwait(false),避免捕获上下文导致死锁:

public async Task<string> ReadDataAsync()
{
    var result = await _db.QueryAsync()
                          .ConfigureAwait(false);
    return result.ToString();
}

自己踩过的坑:在 WinForms/WPF 里如果在 UI 线程上用 .Result 或 .Wait() 阻塞等待异步方法,非常容易死锁。写 UI 代码时正确做法是一路 async 到底,不要在中间同步阻塞。这个坑我在学 Blazor 的时候又踩了一次,后来才彻底记住。

四、常见的错误写法

// 串行:两个请求依次等待
var a = await GetAAsync();
var b = await GetBAsync();

// 并行:同时发起,一起等待
var taskA = GetAAsync();
var taskB = GetBAsync();
await Task.WhenAll(taskA, taskB);
var a = taskA.Result;
var b = taskB.Result;

五、小结

异步编程的核心是"不要阻塞线程",写异步方法要像同步方法一样自然地一路 await 下去。初学的时候最容易犯的错是把异步写成同步阻塞,跑起来没问题但稍微并发就卡死。这篇笔记先记到这里,以后遇到新的坑再补充。