异步编程详解
async/await与Task
概述
C# 的 async/await 是构建异步程序的核心机制,基于 Task 和状态机实现。异步编程可以避免阻塞线程,提升应用的并发能力和响应速度。本文介绍 async/await 的使用方法、常见陷阱和最佳实践。
基础概念
Task 与 Task
| 类型 | 说明 |
|---|---|
| Task | 表示一个异步操作(无返回值) |
| Task | 表示一个异步操作(有返回值) |
| ValueTask | 轻量级 Task,同步路径零分配 |
| void | 仅用于事件处理器(避免使用) |
异步方法命名约定
- 异步方法名以 Async 结尾:
FetchDataAsync - 返回 Task 而非 void(事件处理器除外)
- 使用 CancellationToken 支持取消
快速上手
基本异步方法
// 异步方法:返回 Task<T>
async Task<string> FetchDataAsync(string url) {
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
// 异步方法:返回 Task(无返回值)
async Task SaveDataAsync(Data data) {
var json = JsonSerializer.Serialize(data);
await File.WriteAllTextAsync("data.json", json);
}
// 调用异步方法
var result = await FetchDataAsync("https://api.example.com/data");
await SaveDataAsync(new Data { Value = result });
并行执行多个任务
// 并行执行
var tasks = urls.Select(FetchDataAsync);
var results = await Task.WhenAll(tasks);
// 任意一个完成即返回
var firstResult = await Task.WhenAny(tasks);
// 带超时等待
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try {
var result = await FetchDataAsync(url, cts.Token);
} catch (OperationCanceledException) {
Console.WriteLine("请求超时");
}
详细用法
ValueTask 优化
// ValueTask:同步路径零分配
async ValueTask<int> GetValueAsync(string key) {
if (cache.TryGetValue(key, out var value))
return value; // 同步返回,无堆分配
return await FetchFromDbAsync(key); // 异步路径
}
// 注意:ValueTask 不能被 await 两次,不能并发 await
// 如果需要多次使用,先转换为 Task
ValueTask<int> vt = GetValueAsync("key");
int result = await vt;
// 如果需要多次 await
Task<int> task = vt.AsTask();
var r1 = await task;
// var r2 = await task; // 可以多次 await Task
CancellationToken 取消
// 支持取消的异步方法
async Task<string> FetchWithCancelAsync(
string url, CancellationToken cancellationToken = default) {
using var client = new HttpClient();
var response = await client.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(cancellationToken);
}
// 使用取消令牌
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30)); // 30秒超时
try {
var data = await FetchWithCancelAsync(url, cts.Token);
} catch (OperationCanceledException) {
Console.WriteLine("操作已取消");
}
// 手动取消
cts.Cancel();
异步流(IAsyncEnumerable)
// 异步流:逐步产生数据
async IAsyncEnumerable<int> GenerateNumbersAsync(
[EnumeratorCancellation] CancellationToken ct = default) {
for (int i = 0; i < 100; i++) {
await Task.Delay(100, ct);
yield return i;
}
}
// 消费异步流
await foreach (var number in GenerateNumbersAsync().WithCancellation(ct)) {
Console.WriteLine(number);
if (number > 10) break;
}
常见场景
并发请求限流
// 使用 SemaphoreSlim 限制并发数
var semaphore = new SemaphoreSlim(5); // 最多5个并发
var tasks = urls.Select(async url => {
await semaphore.WaitAsync();
try {
return await FetchDataAsync(url);
} finally {
semaphore.Release();
}
});
var results = await Task.WhenAll(tasks);
重试机制
// 指数退避重试
async Task<T> RetryAsync<T>(
Func<Task<T>> action,
int maxRetries = 3,
int delayMs = 1000) {
for (int attempt = 0; ; attempt++) {
try {
return await action();
} catch (Exception ex) when (attempt < maxRetries) {
var delay = delayMs * (int)Math.Pow(2, attempt);
await Task.Delay(delay);
}
}
}
// 使用 Polly 库(更完善的重试策略)
var policy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, attempt =>
TimeSpan.FromSeconds(Math.Pow(2, attempt)));
var result = await policy.ExecuteAsync(() => FetchDataAsync(url));
注意事项
- 避免使用 async void,异常无法被调用者捕获
- ConfigureAwait(false) 在库代码中使用,避免死锁;应用代码不需要
- 不要使用 .Result 或 .Wait(),会导致死锁
- 异步方法名以 Async 结尾
- 始终传递 CancellationToken,支持取消操作
- 使用 Task.WhenAll 并行执行多个独立任务
进阶用法
任务组合器
// 超时包装器
async Task<T> WithTimeout<T>(Task<T> task, TimeSpan timeout) {
using var cts = new CancellationTokenSource(timeout);
try {
return await task.WaitAsync(cts.Token);
} catch (OperationCanceledException) {
throw new TimeoutException($"操作超时: {timeout}");
}
}
// 使用
var result = await WithTimeout(FetchDataAsync(url), TimeSpan.FromSeconds(10));
Channel 生产者-消费者
// 使用 Channel 实现异步生产者-消费者
var channel = Channel.CreateBounded<int>(100);
// 生产者
async Task ProduceAsync() {
for (int i = 0; i < 1000; i++) {
await channel.Writer.WriteAsync(i);
}
channel.Writer.Complete();
}
// 消费者
async Task ConsumeAsync() {
await foreach (var item in channel.Reader.ReadAllAsync()) {
ProcessItem(item);
}
}
// 启动
await Task.WhenAll(ProduceAsync(), ConsumeAsync());