异步编程 (Async/Await)
什么是异步编程?
核心概念
异步编程是一种编程范式,它允许程序在等待长时间运行的操作(如I/O操作、网络请求等)完成时,不阻塞当前线程,从而提高程序的响应性和吞吐量。
为什么需要异步编程?
🚀 提高响应性 - UI应用不会因为耗时操作而卡顿
⚡ 提升性能 - 服务器应用可以处理更多并发请求
💰 节省资源 - 减少线程数量,降低内存消耗
同步 vs 异步
- 同步方式
- 异步方式
public string GetDataSync()
{
// 阻塞当前线程,直到操作完成
var data = DownloadData(); // 假设需要5秒
ProcessData(data);
return "完成";
}
// 调用时会阻塞
var result = GetDataSync(); // 等待5秒
Console.WriteLine(result);
问题
- 线程被阻塞,无法处理其他任务
- UI应用会出现"无响应"
- Web应用会占用线程池资源
public async Task<string> GetDataAsync()
{
// 不阻塞当前线程,等待时线程可以做其他事
var data = await DownloadDataAsync(); // 假设需要5秒
ProcessData(data);
return "完成";
}
// 调用时不会阻塞
var result = await GetDataAsync(); // 等待时线程可以做其他事
Console.WriteLine(result);
优势
- 线程不被阻塞,可以处理其他请求
- UI保持响应
- 提高服务器吞吐量
Async/Await 基础用法
1. 基本语法
// async 关键字标记方法为异步方法
public async Task<int> CalculateAsync()
{
// await 关键字等待异步操作完成
await Task.Delay(1000); // 模拟耗时操作
return 42;
}
// 无返回值的异步方法
public async Task DoWorkAsync()
{
await Task.Delay(1000);
Console.WriteLine("Work completed");
}
2. 返回类型规则
返回类型 | 使用场景 | 示例 |
---|---|---|
Task | 无返回值的异步方法 | async Task DoWorkAsync() |
Task<T> | 有返回值的异步方法 | async Task<int> GetNumberAsync() |
ValueTask<T> | 性能敏感,可能同步完成 | async ValueTask<int> GetCachedAsync() |
void | ⚠️ 仅用于事件处理器 | async void Button_Click(...) |
避免使用 async void
除了事件处理器外,应避免使用 async void
:
- 无法 await
- 异常无法被捕获
- 难以测试
实战案例
案例1:并行执行多个异步任务
- ❌ 顺序执行(慢)
- ✅ 并行执行(快)
public async Task<string> GetAllDataSequentialAsync()
{
// 总耗时:3 + 2 + 4 = 9秒
var user = await GetUserAsync(); // 3秒
var orders = await GetOrdersAsync(); // 2秒
var products = await GetProductsAsync(); // 4秒
return $"{user}, {orders}, {products}";
}
public async Task<string> GetAllDataParallelAsync()
{
// 同时启动所有任务
var userTask = GetUserAsync();
var ordersTask = GetOrdersAsync();
var productsTask = GetProductsAsync();
// 等待所有任务完成,总耗时:max(3, 2, 4) = 4秒
await Task.WhenAll(userTask, ordersTask, productsTask);
return $"{userTask.Result}, {ordersTask.Result}, {productsTask.Result}";
}
案例2:超时控制
public async Task<string> GetDataWithTimeoutAsync()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
var data = await GetDataAsync(cts.Token);
return data;
}
catch (OperationCanceledException)
{
return "操作超时";
}
}
案例3:异步流 (IAsyncEnumerable)
.NET Core 3.0+
// 生成异步流
public async IAsyncEnumerable<int> GenerateNumbersAsync()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100); // 模拟异步操作
yield return i;
}
}
// 消费异步流
public async Task ConsumeNumbersAsync()
{
await foreach (var number in GenerateNumbersAsync())
{
Console.WriteLine(number);
}
}
常见问题与最佳实践
❌ 错误做法
1. 阻塞异步代码(死锁风险)
// ❌ 不要这样做!可能导致死锁
public void BadMethod()
{
var result = GetDataAsync().Result; // 阻塞等待
var result2 = GetDataAsync().GetAwaiter().GetResult(); // 同样不好
}
死锁原因
在UI或ASP.NET同步上下文中,.Result
或 .Wait()
会阻塞当前线程,而异步方法完成后需要回到原线程,导致死锁。
2. async void 方法
// ❌ 不要这样做!
public async void ProcessDataAsync()
{
await Task.Delay(1000);
throw new Exception("无法捕获!");
}
// ✅ 应该这样做
public async Task ProcessDataAsync()
{
await Task.Delay(1000);
// 异常可以被调用方捕获
}
3. 不必要的 async/await
// ❌ 不必要的 async/await
public async Task<string> GetDataAsync()
{
return await _httpClient.GetStringAsync(url);
}
// ✅ 直接返回 Task
public Task<string> GetDataAsync()
{
return _httpClient.GetStringAsync(url);
}
✅ 最佳实践
异步编程指南
- 一路异步到底 - 不要混用同步和异步代码
- 使用 ConfigureAwait(false) - 在类库代码中避免捕获上下文
- 取消令牌 - 长时间运行的操作应支持取消
- 避免 async void - 除了事件处理器
- 并行执行 - 使用
Task.WhenAll
并行执行独立任务 - 异常处理 - 使用 try-catch 包裹 await 语句
ConfigureAwait 详解
// 在类库代码中
public async Task<string> GetDataAsync()
{
// ConfigureAwait(false) 避免捕获同步上下文,提高性能
var data = await _httpClient.GetStringAsync(url).ConfigureAwait(false);
return ProcessData(data);
}
// 在UI代码中
private async void Button_Click(object sender, EventArgs e)
{
// 不使用 ConfigureAwait,需要回到UI线程更新界面
var data = await GetDataAsync();
textBox.Text = data; // 必须在UI线程执行
}
何时使用 ConfigureAwait(false)?
- ✅ 类库代码
- ✅ 不需要特定同步上下文的代码
- ❌ 需要更新UI的代码
- ❌ 需要访问 HttpContext 的ASP.NET代码
Web API 中的异步
ASP.NET Core Controller
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
[HttpGet]
public async Task<ActionResult<List<Product>>> GetProductsAsync()
{
// 异步查询数据库
var products = await _productService.GetAllAsync();
return Ok(products);
}
[HttpPost]
public async Task<ActionResult<Product>> CreateProductAsync(Product product)
{
// 异步保存数据
var created = await _productService.CreateAsync(product);
return CreatedAtAction(nameof(GetProductsAsync), new { id = created.Id }, created);
}
}
性能对比
基准测试示例
public class AsyncBenchmark
{
[Benchmark]
public void SyncMethod()
{
// 同步处理100个请求
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10); // 模拟IO操作
}
}
[Benchmark]
public async Task AsyncMethod()
{
// 异步处理100个请求
var tasks = Enumerable.Range(0, 100)
.Select(_ => Task.Delay(10));
await Task.WhenAll(tasks);
}
}
方法 | 耗时 | 线程使用 |
---|---|---|
同步 | ~1000ms | 1个线程持续占用 |
异步 | ~10ms | 线程可以处理其他请求 |
总结
关键要点
- 异步编程通过
async
/await
提高应用响应性和性能 - 使用
Task
和Task<T>
作为返回类型 - 避免阻塞异步代码,一路异步到底
- 使用
Task.WhenAll
并行执行独立任务 - 在类库中使用
ConfigureAwait(false)
- 支持取消操作和超时控制