C# 异步编程

2 minIntermediate2026/6/14

async/await、Task/ValueTask、ConfigureAwait、并行编程(TPL)、Channel、IAsyncEnumerable、异步流、常见陷阱

1. async/await 基础

1.1 异步编程模型

C# 的 async/await 是语言级别的异步编程支持,让异步代码像同步代码一样直观:

// 同步方法 - 阻塞线程
public string FetchData()
{
    var client = new HttpClient();
    var response = client.GetStringAsync("https://api.example.com/data").Result; // 阻塞!
    return response;
}

// 异步方法 - 不阻塞线程
public async Task<string> FetchDataAsync()
{
    var client = new HttpClient();
    var response = await client.GetStringAsync("https://api.example.com/data");
    return response;
}

1.2 异步方法签名

// 返回 Task(无结果)
public async Task DoWorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("工作完成");
}

// 返回 Task<T>(有结果)
public async Task<int> CalculateAsync()
{
    await Task.Delay(500);
    return 42;
}

// 返回 ValueTask<T>(避免分配,结果可能同步可用)
public async ValueTask<int> GetCachedValueAsync()
{
    if (_cache.TryGetValue("key", out int value))
        return value; // 同步返回,无堆分配

    value = await ExpensiveOperationAsync();
    _cache["key"] = value;
    return value;
}

// 返回 void(仅事件处理器使用!)
public async void OnButtonClick(object sender, EventArgs e)
{
    await LoadDataAsync(); // 异常无法被调用方捕获
}

1.3 异步流 (IAsyncEnumerable)

// C# 8+ 异步流 - 逐项异步产出
public async IAsyncEnumerable<string> StreamDataAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    for (int i = 0; i < 100; i++)
    {
        ct.ThrowIfCancellationRequested();
        await Task.Delay(100, ct);
        yield return $"数据项 {i}";
    }
}

// 消费异步流
await foreach (var item in StreamDataAsync().WithCancellation(ct))
{
    Console.WriteLine(item);
}

// 配置异步枚举的上下文
await foreach (var item in StreamDataAsync()
    .ConfigureAwait(false))
{
    // 不捕获同步上下文
}

2. Task 详解

2.1 创建与启动

// Task.Run - 在线程池上执行
Task<int> task1 = Task.Run(() =>
{
    Thread.Sleep(1000);
    return 42;
});

// Task.Factory.StartNew - 更多控制
Task<int> task2 = Task.Factory.StartNew(() =>
{
    return ComputeHeavy();
}, CancellationToken.None,
   TaskCreationOptions.LongRunning,  // 长时间运行任务
   TaskScheduler.Default);

// Task.CompletedTask - 已完成的空任务
return Task.CompletedTask;

// Task.FromResult - 同步结果包装为 Task
return Task.FromResult(42);

// Task.FromException / Task.FromCanceled
return Task.FromException<int>(new InvalidOperationException());

2.2 组合多个 Task

var task1 = FetchUserAsync(1);
var task2 = FetchUserAsync(2);
var task3 = FetchUserAsync(3);

// 等待所有完成
User[] users = await Task.WhenAll(task1, task2, task3);

// 等待任一完成
Task<User> first = await Task.WhenAny(task1, task2, task3);

// 超时控制
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
    var result = await FetchDataAsync(cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("请求超时");
}

// 批量处理(控制并发数)
var tasks = ids.Select(id => FetchUserAsync(id));
var results = await Task.WhenAll(tasks);

2.3 任务链 (Continuation)

// ContinueWith - 显式任务链(不推荐,优先用 await)
task.ContinueWith(t =>
{
    if (t.IsFaulted) { /* 处理异常 */ }
    else { var result = t.Result; }
}, TaskContinuationOptions.OnlyOnRanToCompletion);

// 使用 await 更清晰
try
{
    var result = await task;
}
catch (Exception ex)
{
    // 处理异常
}

3. ConfigureAwait

// 库代码中应始终使用 ConfigureAwait(false)
public async Task<string> LibraryMethodAsync()
{
    var data = await httpClient.GetStringAsync(url)
        .ConfigureAwait(false); // 不捕获同步上下文

    // 此后不在原始上下文执行(避免死锁)
    var processed = ProcessData(data);
    return processed;
}

// UI 应用中需要更新 UI 时使用默认(捕获上下文)
public async void OnButtonClick(object sender, EventArgs e)
{
    var data = await FetchDataAsync(); // 捕获 UI 上下文
    label.Text = data; // 安全更新 UI
}

// ASP.NET Core 无同步上下文,ConfigureAwait 无实际效果
// 但库代码仍建议使用,因为可能在其他上下文中使用

3.1 死锁场景

// WinForms/WPF 中的经典死锁
public void DeadlockExample()
{
    // 同步等待异步方法 → 死锁!
    var result = FetchDataAsync().Result;

    // 同样会死锁
    FetchDataAsync().Wait();
}

// 正确做法:一路 async 到底
public async Task CorrectExample()
{
    var result = await FetchDataAsync();
}

4. 并行编程 (TPL)

4.1 Parallel

// Parallel.For
Parallel.For(0, 1000, i =>
{
    ProcessItem(i);
});

// Parallel.ForEach
var items = Enumerable.Range(0, 1000).ToList();
Parallel.ForEach(items, item =>
{
    ProcessItem(item);
});

// 带分区器控制
Parallel.ForEach(Partitioner.Create(0, 1000), range =>
{
    for (int i = range.Item1; i < range.Item2; i++)
    {
        ProcessItem(i);
    }
});

// Parallel.ForEachAsync (.NET 6+)
await Parallel.ForEachAsync(urls, new ParallelOptions
{
    MaxDegreeOfParallelism = 10
}, async (url, ct) =>
{
    await DownloadAsync(url, ct);
});

// Parallel.Invoke
Parallel.Invoke(
    () => ComputeA(),
    () => ComputeB(),
    () => ComputeC()
);

4.2 ParallelLoopState 控制

Parallel.ForEach(items, (item, state) =>
{
    if (item.IsInvalid)
    {
        state.Stop(); // 尽快停止所有迭代
        return;
    }

    if (item.ShouldSkip)
    {
        state.Break(); // 停止当前项之后的迭代
        return;
    }

    ProcessItem(item);
});

5. Channel

Channel 是高性能的异步生产者-消费者模型(.NET Core 3.0+):

using System.Threading.Channels;

// 创建 Channel
var channel = Channel.CreateBounded<string>(capacity: 100);
// var channel = Channel.CreateUnbounded<string>();

// 生产者
async Task ProduceAsync(ChannelWriter<string> writer, CancellationToken ct)
{
    for (int i = 0; i < 1000; i++)
    {
        ct.ThrowIfCancellationRequested();
        await writer.WriteAsync($"消息 {i}", ct);
    }
    writer.Complete(); // 标记完成
}

// 消费者
async Task ConsumeAsync(ChannelReader<string> reader, CancellationToken ct)
{
    await foreach (var message in reader.ReadAllAsync(ct))
    {
        Console.WriteLine($"收到: {message}");
    }
}

// 使用
var cts = new CancellationTokenSource();
var producerTask = ProduceAsync(channel.Writer, cts.Token);
var consumerTask = ConsumeAsync(channel.Reader, cts.Token);
await Task.WhenAll(producerTask, consumerTask);

5.1 多生产者多消费者

var channel = Channel.CreateBounded<int>(new BoundedChannelOptions(500)
{
    FullMode = BoundedChannelFullMode.Wait,   // 满时等待
    SingleReader = false,                      // 多消费者
    SingleWriter = false                       // 多生产者
});

// 启动多个生产者
var producers = Enumerable.Range(0, 3)
    .Select(i => ProduceAsync(channel.Writer, i));

// 启动多个消费者
var consumers = Enumerable.Range(0, 2)
    .Select(i => ConsumeAsync(channel.Reader, i));

await Task.WhenAll(
    Task.WhenAll(producers),
    channel.Writer.CompleteAsync().AsTask()
).ContinueWith(_ => Task.WhenAll(consumers));

6. 常见异步陷阱

6.1 陷阱与解决方案

//  陷阱1:async void(异常无法捕获)
public async void BadMethod()
{
    await Task.Delay(100);
    throw new Exception("崩溃!"); // 未观察到的异常
}

//  解决:返回 Task
public async Task GoodMethod()
{
    await Task.Delay(100);
    throw new Exception("可控异常");
}

//  陷阱2:.Result / .Wait() 死锁
var result = asyncMethod.Result;

//  解决:await
var result = await asyncMethod();

//  陷阱3:不必要的 async/await(多余的状态机)
public async Task<int> UnnecessaryAsync()
{
    return await Task.FromResult(42); // 不需要 async
}

//  解决:直接返回 Task
public Task<int> BetterAsync()
{
    return Task.FromResult(42);
}

//  陷阱4:忘记 await
public async Task ForgetAwaitAsync()
{
    DoSomethingAsync(); // 忘记 await,异常丢失
}

//  解决:始终 await
public async Task CorrectAwaitAsync()
{
    await DoSomethingAsync();
}

//  陷阱5:在循环中 await(串行执行)
foreach (var url in urls)
{
    await DownloadAsync(url); // 串行,慢
}

//  解决:并行执行
await Task.WhenAll(urls.Select(url => DownloadAsync(url)));

6.2 取消模式

public async Task<long> DownloadWithProgressAsync(
    string url,
    IProgress<double>? progress = null,
    CancellationToken ct = default)
{
    using var response = await _httpClient.GetAsync(url,
        HttpCompletionOption.ResponseHeadersRead, ct);

    var totalBytes = response.Content.Headers.ContentLength ?? -1;
    var buffer = new byte[8192];
    long bytesRead = 0;

    using var stream = await response.Content.ReadAsStreamAsync(ct);
    int read;
    while ((read = await stream.ReadAsync(buffer, ct)) > 0)
    {
        bytesRead += read;
        progress?.Report((double)bytesRead / totalBytes);
    }

    return bytesRead;
}

7. 异步最佳实践

场景建议原因
库方法ConfigureAwait(false)避免死锁,提升性能
事件处理器async void唯一允许 async void 的场景
热路径同步结果ValueTask<T>避免不必要的堆分配
批量异步操作Task.WhenAll并行执行,提升吞吐
流式数据IAsyncEnumerable<T>逐项产出,降低延迟
生产消费模式Channel<T>高性能,背压支持
超时控制CancellationTokenSource可取消,可组合
进度报告IProgress<T>线程安全的进度回调