异步编程 async/await

翻译整理自 Async Programming 与 Task。

1. async / await 基本用法

public async Task<string> FetchAsync(string url)
{
    using var http = new HttpClient();
    var text = await http.GetStringAsync(url);
    return text;
}

var t = await FetchAsync("https://example.com");
Console.WriteLine(t);
  • async 标记方法为异步
  • await 等待 Task 完成并自动切回原上下文
  • 返回类型 Task / Task<T> / ValueTask<T>

2. Task 创建

var t1 = Task.Run(() => Work(1));
var t2 = Task.Factory.StartNew(() => Work(2));

await Task.WhenAll(t1, t2);
await Task.Delay(1000);
var first = await Task.WhenAny(t1, t2);

3. 并行与顺序

// 串行
await A();
await B();

// 并行
await Task.WhenAll(A(), B());

// 第一个完成
var r = await Task.WhenAny(servers.Select(s => s.PingAsync()));

4. Task 返回值

async Task<int> ComputeAsync()
{
    await Task.Delay(100);
    return 42;
}

int n = await ComputeAsync();

5. 异步取消 CancellationToken

public async Task RunAsync(CancellationToken token)
{
    for (int i = 0; i < 1000; i++)
    {
        token.ThrowIfCancellationRequested();
        await Task.Delay(10, token);
    }
}

using var cts = new CancellationTokenSource();
cts.CancelAfter(500);
try { await RunAsync(cts.Token); }
catch (OperationCanceledException) { }

6. 异常处理

try
{
    await Task.WhenAll(t1, t2);
}
catch (Exception e) when (e is not TaskCanceledException)
{
    Console.WriteLine(e.Message);
}

WhenAll 把所有异常包装为 AggregateException,但 await 时只抛第一个。

7. ValueTask

值类型 Task,避免堆分配:

public async ValueTask<int> GetOrCompute()
{
    if (cached is not null) return cached.Value;   // 同步路径无分配
    await Task.Delay(10);
    return 0;
}

8. ConfigureAwait

await task.ConfigureAwait(false);     // 不切回原上下文,库代码推荐
await task.ConfigureAwait(true);      // 切回捕获的上下文(默认)

库(NuGet 包)应使用 ConfigureAwait(false);UI 应用避免 deadlock。

9. async streams(C# 8+)

public async IAsyncEnumerable<int> StreamAsync(CancellationToken ct = default)
{
    for (int i = 0; i < 10; i++)
    {
        await Task.Delay(100, ct);
        yield return i;
    }
}

await foreach (var x in StreamAsync())
    Console.WriteLine(x);

10. 同步上下文与 SynchronizationContext

  • UI 应用(WPF/WinForms)有同步上下文,await 后切回 UI 线程
  • ASP.NET Core 无同步上下文,所有 await 后任意线程

11. Task.Run vs 调用异步 API

// ❌ 反模式:把 IO 异步包装在线程池
var r = await Task.Run(() => httpClient.GetStringAsync(url));

// ✅ 直接调用
var r = await httpClient.GetStringAsync(url);

CPU 密集才用 Task.Run。

12. Parallel 并行循环

Parallel.For(0, 1000, i => Work(i));
Parallel.ForEach(items, item => Work(item));

var sum = ParallelEnumerable.Range(0, 1000).Sum();

13. Channel 与生产消费

using System.Threading.Channels;

var ch = Channel.CreateUnbounded<int>();

// 生产
_ = Task.Run(async () =>
{
    for (int i = 0; i < 10; i++)
        await ch.Writer.WriteAsync(i);
    ch.Writer.Complete();
});

// 消费
await foreach (var x in ch.Reader.ReadAllAsync())
    Console.WriteLine(x);

小结

async/await 是 C# 异步编程的核心,简洁、易读、易维护,配合 Task、CancellationToken、Channel 形成现代高并发模型。