8.2 Task与async/await
概述
在现代C#开发中,异步编程已成为提高应用程序响应能力和资源利用率的关键技术。Task和async/await是.NET框架中实现异步编程的核心机制,它们使得编写异步代码变得简单直观,同时保持了代码的可读性和可维护性。
Task类基础
什么是Task
Task类表示一个异步操作,位于System.Threading.Tasks命名空间- 可以表示已完成、正在运行或将要运行的异步操作
- 提供了检查任务状态、等待完成和获取结果的方法
创建和启动Task
// 使用Task.Run创建并启动后台任务
Task.Run(() => {
// 长时间运行的操作
Console.WriteLine("任务正在运行...");
});
// 使用Task.Factory.StartNew
Task.Factory.StartNew(() => {
// 工作代码
});
任务状态与生命周期
Created:任务已初始化但尚未计划WaitingForActivation:任务等待激活Running:任务正在执行RanToCompletion:任务成功完成Faulted:任务因未处理异常而完成Canceled:任务因取消而完成
async/await模式
基本语法
public async Task<string> GetDataAsync()
{
// 模拟异步操作
await Task.Delay(1000);
return "数据已加载";
}
关键概念
- async修饰符:标记方法为异步方法
- await运算符:暂停方法执行,直到等待的任务完成
- 返回类型:通常为
Task、Task<T>或ValueTask<T>
执行流程
- 调用异步方法时,同步执行直到第一个await
- 遇到await时,方法返回一个Task,控制权交回调用者
- 当await的任务完成后,方法从await处恢复执行
异常处理
捕获异步异常
try
{
await SomeAsyncOperation();
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
多个任务的异常处理
try
{
Task task1 = Task1Async();
Task task2 = Task2Async();
await Task.WhenAll(task1, task2);
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
任务组合
Task.WhenAll
等待所有提供的任务完成
Task<int> task1 = GetValue1Async();
Task<int> task2 = GetValue2Async();
int[] results = await Task.WhenAll(task1, task2);
Task.WhenAny
等待任何一个提供的任务完成
Task<string> task1 = DownloadFromSource1Async();
Task<string> task2 = DownloadFromSource2Async();
Task<string> firstFinished = await Task.WhenAny(task1, task2);
string result = await firstFinished;
性能考虑
避免async void
- 只应在事件处理程序中使用
async void - 其他情况应使用
async Task,以便调用者可以await和捕获异常
ConfigureAwait
await SomeAsyncOperation().ConfigureAwait(false);
- 当不需要返回到原始上下文时使用,可提高性能
- 特别适用于库代码
ValueTask
对于可能同步完成的操作,考虑使用ValueTask<T>
public ValueTask<int> GetCachedValueAsync()
{
if (cache.TryGetValue(key, out var value))
return new ValueTask<int>(value);
return new ValueTask<int>(LoadValueAsync());
}
实际应用示例
异步HTTP请求
public async Task<string> GetWebContentAsync(string url)
{
using (HttpClient client = new HttpClient())
{
return await client.GetStringAsync(url);
}
}
并行处理
public async Task ProcessMultipleItemsAsync(IEnumerable<Item> items)
{
var tasks = items.Select(item => ProcessItemAsync(item));
await Task.WhenAll(tasks);
}
常见陷阱与最佳实践
- 避免死锁:不要在UI线程上使用
.Result或.Wait() - 合理使用异步:不是所有方法都需要异步,仅对I/O密集型或长时间运行的操作使用
- 命名约定:异步方法应以"Async"后缀命名
- 取消支持:考虑支持
CancellationToken - 避免过度并行:控制并发任务数量
总结
Task和async/await为C#开发者提供了强大而优雅的异步编程模型。通过合理使用这些特性,可以编写出高效、响应迅速的应用程序,同时保持代码的清晰和可维护性。掌握这些概念是现代C#开发者的必备技能。
