前置知识: C#

异步编程详解

00:00
2 min Intermediate 2026/6/14

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());

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式