c# async 和 await 详解

来源:这里教程网 时间:2026-02-21 17:37:32 作者:

async/await 不是多线程,也不是“自动提速”的魔法——它只是让 IO 等待不卡主线程,且代码写起来像同步一样直白。

async 方法必须返回 Task 或 Task,别用 void(除非 UI 事件处理)

返回

void
的 async 方法无法被
await
,也无法传播异常,调试时会静默失败。只在 Windows Forms/WPF 的事件处理器中允许使用,例如:
private async void button1_Click(...)
。其他所有场景,一律用
Task
Task<t></t>

Task
:表示“有异步操作,但不返回值”(如保存日志、发送通知)
Task<int></int>
:表示“操作完成后返回一个
int
”,函数体内直接
return 42;
,编译器自动包装成
Task.FromResult(42)
千万别写
async Task MyMethod() { await SomeAsync(); return; }
—— 返回类型是
Task
就够了,
return;
是冗余的

await 不会阻塞线程,但它会让方法“暂停并交出控制权”

当你写

await client.GetStringAsync(url)
,当前方法立即返回(比如返回一个未完成的
Task<string></string>
),调用方可以继续执行;等网络响应回来,后续代码才在合适的上下文(如 UI 线程或线程池线程)中恢复。关键点:

await 后面的对象必须是“可等待的”(实现
GetAwaiter()
,通常是
Task
ValueTask
如果 await 的是已结束的 Task(比如
Task.FromResult("done")
),不会真正暂停,直接往下走
await 不等于“新开线程”——HttpClient 的请求本身是 IO 完成端口驱动的,不占线程池线程

别在同步方法里“假装 await”:没有 async 就不能用 await

下面这段代码会编译报错:

CS4032:The 'await' operator can only be used within an async method

public string GetData()
{
    var result = await httpClient.GetStringAsync("https://api.example.com"); // ❌ 编译不过
    return result;
}

正确做法是向上一层推:调用方也得是 async,形成“异步链条”:

public async Task<string> GetDataAsync() // ✅ 加 async,改返回 Task<string>
{
    var result = await httpClient.GetStringAsync("https://api.example.com"); // ✅ 合法
    return result;
}
// 调用处也要 await(或 .GetAwaiter().GetResult() —— 仅限测试/极少数阻塞场景)
public async Task HandleRequest()
{
    string data = await GetDataAsync(); // ✅
}
禁止在构造函数、属性 getter/setter、终结器中用 async/await(语法不允许) 不要为了“看起来异步”而强行加
Task.Run(() => SyncMethod())
包装 CPU 密集型操作——这反而增加调度开销,应改用
Task.Run
显式卸载到线程池,且调用方需理解这是真多线程

常见陷阱:ConfigureAwait(false) 和 UI 线程死锁

在类库(如 NuGet 包)或后台服务中,建议对所有

await
加上
.ConfigureAwait(false)

var html = await httpClient.GetStringAsync(url).ConfigureAwait(false);
默认
ConfigureAwait(true)
会尝试回到“原始上下文”(如 WinForms 的 UI 线程),若该上下文正被同步阻塞(比如调用了
.Result
.Wait()
),就会死锁
类库不应假设调用方上下文,所以一律
false
;UI 层(如 WPF 的 ViewModel)若需更新控件,则保留默认(即不写
ConfigureAwait
),由框架自动调度回 UI 线程
ASP.NET Core 默认无同步上下文,
ConfigureAwait(false)
影响不大,但加上更稳妥

最常被忽略的一点:async/await 的价值不在“快”,而在“不卡”。它解决的是资源等待时的线程空转问题,不是替代算法优化或数据库索引。写错一个

await
可能导致整个请求线程被占住,尤其在高并发 Web API 中——这种问题不会报错,只会悄悄拖慢吞吐量。

相关推荐