悠悠楠杉
C的async和await怎么配合使用?
一、异步编程的本质需求
在桌面应用开发中(比如WPF),当我们需要从网络加载数据时,同步代码会导致界面"假死"——这是因为UI线程被阻塞。传统解决方案是使用回调或事件,但会导致著名的"回调地狱"。
csharp
// 传统回调方式
client.DownloadStringAsync(url, result => {
Dispatcher.Invoke(() => {
textBox.Text = result;
});
});
async/await的诞生正是为了解决这类问题。某电商平台的后端工程师李工告诉我:"自从全面采用async/await,我们的API吞吐量提升了40%,而且代码可读性大幅改善"。
二、编译器背后的魔法
当你在方法前加上async关键字时,编译器会进行一系列代码转换:
- 将方法拆分为多个状态机片段
- 自动生成
IAsyncStateMachine
实现 - 创建
Task
对象作为返回容器
csharp
// 原始代码
public async Task
var client = new HttpClient();
return await client.GetStringAsync("...");
}
// 编译器生成类似(简化版):
class StateMachine : IAsyncStateMachine {
int _state;
TaskAwaiter
void MoveNext() {
if (_state == 0) {
_awaiter = client.GetStringAsync("...").GetAwaiter();
if (!_awaiter.IsCompleted) {
_state = 1;
_awaiter.OnCompleted(MoveNext);
return;
}
}
// 后续处理...
}
}
三、关键使用准则
1. 避免async void陷阱
csharp
// 错误示范 - 异常无法捕获
async void ButtonClick() {
throw new Exception("这将崩溃应用!");
}
// 正确做法
async Task ButtonClickAsync() {
await Task.Delay(100);
}
2. 配置上下文流转
csharp
// UI线程需要上下文
async Task UpdateUIAsync() {
await Task.Delay(100); // 自动回到UI线程
textBox.Text = "Done";
}
// 服务端代码应避免上下文捕获
async Task ProcessRequestAsync() {
await Task.Delay(100).ConfigureAwait(false);
// 后续代码在线程池执行
}
3. 并行优化模式
csharp
// 顺序执行 - 总耗时200ms
var a = await GetAAsync();
var b = await GetBAsync();
// 并行执行 - 总耗时100ms
var taskA = GetAAsync();
var taskB = GetBAsync();
await Task.WhenAll(taskA, taskB);
四、性能关键点
- 状态机分配:每个async方法会产生约100字节的堆内存分配
- 线程池震荡:突然的大量异步任务会触发ThreadPool扩容
- 同步上下文开销:不必要的上下文切换可能增加10-20%开销
某金融系统性能测试数据显示:
| 场景 | 同步调用 | 正确异步 | 错误异步 |
|------|---------|----------|----------|
| QPS | 1200 | 8500 | 3200 |
| 延迟 | 220ms | 45ms | 180ms |
五、进阶模式
1. 取消令牌集成
csharp
async Task LongOperationAsync(CancellationToken token) {
while(!token.IsCancellationRequested) {
await Task.Delay(100, token);
}
}
2. 值任务优化
csharp
public async ValueTask
if (_cache.TryGetValue(key, out var value))
return value;
return await FetchFromDbAsync(key);
}
3. IAsyncEnumerable流式处理
csharp
async IAsyncEnumerable<string> ReadLinesAsync() {
using var stream = File.OpenText("data.txt");
while (!stream.EndOfStream) {
yield return await stream.ReadLineAsync();
}
}
六、常见误区诊断
案例:某团队在ASP.NET Core中混合使用.Result
导致死锁:
csharp
// 控制器方法
public ActionResult GetData() {
return _service.GetDataAsync().Result; // 死锁!
}
解决方案矩阵:
| 场景 | 推荐方案 | 替代方案 |
|------|----------|----------|
| MVC控制器 | async/await全链路 | 改用中间件 |
| 单元测试 | AsyncContext.Run | Wait+ConfigureAwait |
| 控制台程序 | MainAsync模式 | .GetAwaiter().GetResult() |
通过正确理解async/await的协作机制,开发者可以编写出既高效又易维护的异步代码。记住微软工程师Stephen Toub的忠告:"异步代码不是为了让东西跑得更快,而是为了在等待时不做无谓的阻塞。"`