C# 异步编程入门
异步编程是现代 C#(.NET)开发中的基础范式,能让您构建响应迅速且性能卓越的应用程序。与同步方法(代码执行会阻塞当前线程直至操作完成)不同,异步代码在等待耗时操作(例如数据库查询、HTTP 调用或文件读取)的结果时,会释放当前线程去执行其他任务。
随着 C# 5.0 中引入 async 和 await 关键字,异步编程不再复杂,变得对每位开发者都触手可及。在本指南中,我们将逐一解析所有关键概念:从 Task 和 ValueTask 的基础知识到错误处理与操作取消。
async/await 与 Task 基础
什么是 Task 与 Task<TResult>
Task 是 TPL(任务并行库)中的核心类型,代表一个异步操作。Task(无返回值)和 Task<TResult>(有返回值)是构建异步代码的基本单元。
// 无返回值的简单异步方法public async Task DoSomethingAsync(){ await Task.Delay(1000); Console.WriteLine("Done!");}
// 有返回值的异步方法public async Task<int> CalculateSumAsync(int a, int b){ await Task.Delay(500); // 模拟一个耗时操作 return a + b;}async 和 await 关键字
async 修饰符告诉编译器该方法包含异步代码。await 运算符会暂停方法的执行,直到等待的任务完成,且不会阻塞线程。
public async Task ProcessDataAsync(){ Console.WriteLine("开始处理..."); // 异步等待 —— 线程未被阻塞 string result = await FetchDataFromApiAsync(); Console.WriteLine($"接收到结果: {result}");}使用 async/await 的规则
- 不要使用 async void(事件处理程序除外)—— 这会导致异常无法被追踪。
- 避免阻塞调用(如
.Result、.Wait())—— 它们可能导致死锁。 - 为异步方法命名时添加 "Async" 后缀以保持清晰性。
进阶技巧:ValueTask、CancellationToken 与并发
ValueTask 与 Task 对比
ValueTask 是一种优化方案,适用于异步操作经常同步完成(例如缓存数据)的场景。与引用类型的 Task 不同,ValueTask 是一个结构体,可以减少垃圾回收器的负担。
public ValueTask<int> GetCachedOrFetchAsync(int id){ if (cache.ContainsKey(id)) { // 同步完成 —— 无内存分配 return new ValueTask<int>(cache[id]); } // 异步完成 return new ValueTask<int>(FetchFromDatabaseAsync(id));}使用 CancellationToken 取消操作
CancellationToken 允许您优雅地中断长时间运行的异步操作。如果方法接受取消令牌,请始终传递它。
public async Task<string> DownloadFileAsync(string url, CancellationToken ct){ using var client = new HttpClient(); var response = await client.GetAsync(url, ct); // 将 ct 传递给 API return await response.Content.ReadAsStringAsync();}
// 用法var cts = new CancellationTokenSource();cts.CancelAfter(TimeSpan.FromSeconds(5)); // 5 秒超时
try{ string data = await DownloadFileAsync("https://example.com", cts.Tok