共计 1628 个字符,预计需要花费 5 分钟才能阅读完成。
为什么需要异步编程?
刚开始学 C# 时,我总喜欢写这样的代码:

var result = LongRunningOperation(); // 同步阻塞调用
Console.WriteLine(result);
直到有一天做了一个文件处理工具,点击按钮后整个 UI 界面卡死 5 秒——这才意识到同步编程的问题:
- UI 线程冻结:主线程被阻塞导致界面无响应
- 资源浪费:线程空转等待 I / O 操作(如数据库查询、文件读写)
- 吞吐量瓶颈:服务器应用无法有效利用线程池
异步编程核心机制
async/await 的工作原理就像外卖点单:
- 调用异步方法(下单)
- await 时释放当前线程(不用站在门口等外卖)
- 操作完成后回调(外卖送到按门铃)
关键点在于SynchronizationContext:
- UI 程序会自动回到 UI 线程执行后续代码
- ASP.NET Core 没有同步上下文,默认在线程池继续执行
- 可通过
ConfigureAwait(false)显式放弃上下文
实战代码示例
基础 HTTP 请求
public async Task<string> GetWebContentAsync(string url, CancellationToken ct)
{using var client = new HttpClient();
try {return await client.GetStringAsync(url, ct)
.ConfigureAwait(false);
}
catch (TaskCanceledException) {Console.WriteLine("请求被取消");
return string.Empty;
}
}
并发任务处理
public async Task<Dictionary<string, string>> DownloadAllAsync(IEnumerable<string> urls)
{
var tasks = urls.Select(async url =>
new KeyValuePair<string, string>(url, await GetWebContentAsync(url)));
var results = await Task.WhenAll(tasks);
return results.ToDictionary(p => p.Key, p => p.Value);
}
异步流处理
public async IAsyncEnumerable<string> ReadLinesAsync(string filePath)
{using var reader = File.OpenText(filePath);
while (!reader.EndOfStream)
{yield return await reader.ReadLineAsync();
}
}
五大避坑指南
- async void 是火坑:异常会直接崩溃进程,事件处理器除外
- 死锁陷阱 :
ConfigureAwait(false)是 WinForms/WPF 的保命符 - 线程池过载 :并行度超过
Environment.ProcessorCount时考虑限流 - 任务类型混淆:I/ O 密集型用 async,CPU 密集型用
Task.Run - 取消令牌必传:所有异步方法都应支持
CancellationToken
性能实测数据
使用 BenchmarkDotNet 测试文件读取(1GB 数据):
| 方式 | 耗时(ms) | 内存分配(MB) |
|---|---|---|
| 同步读取 | 1200 | 1024 |
| 异步读取 | 450 | 32 |
思考题
- 长时间运行任务应该:
- 定期检查
ct.IsCancellationRequested -
使用
CancellationTokenSource.CreateLinkedTokenSource组合令牌 -
ASP.NET Core 没有同步上下文,天然防死锁
-
ValueTask适用场景: - 高频调用的热路径方法
- 可能同步返回结果的方法(如缓存命中时)
异步编程就像学骑自行车——开始可能摔几次,但掌握后出行效率倍增。建议从小的 I / O 操作开始练习,逐步应用到复杂场景。遇到问题多查看 Task.Status 和异常堆栈,很快就能得心应手。
正文完
