1. 异步编程基础:理解Async/Await机制
在C#中,async/await是处理异步操作的核心语法糖。当我们在方法声明前加上async关键字时,实际上是在告诉编译器这个方法内部会包含await表达式。编译器会将这个方法转换为状态机,使得异步操作能够以近乎同步的方式编写。
csharp复制public async Task<string> FetchDataAsync()
{
var result = await HttpClient.GetStringAsync("https://api.example.com/data");
return result.ToUpper();
}
这段代码的执行流程是:
- 调用FetchDataAsync方法时,遇到第一个await表达式
- 控制权返回给调用者,方法返回一个Task
- 当HTTP请求完成时,方法从await处恢复执行
- 最后返回处理后的字符串
重要提示:async方法应该总是返回Task、Task
或ValueTask/ValueTask 。避免使用async void,除非是事件处理程序。
2. Task.WhenAll的深度解析
Task.WhenAll是处理多个并行异步操作的利器。它接受一组Task并返回一个新的Task,这个新Task在所有输入Task完成时才会完成。
2.1 基本用法
csharp复制public async Task DownloadAllAsync(string[] urls)
{
var downloadTasks = urls.Select(url =>
HttpClient.GetStringAsync(url)).ToArray();
// 等待所有下载完成
string[] contents = await Task.WhenAll(downloadTasks);
foreach(var content in contents)
{
Console.WriteLine($"Downloaded {content.Length} bytes");
}
}
2.2 错误处理策略
当使用WhenAll时,如果有多个任务失败,所有异常会被聚合到一个AggregateException中:
csharp复制try
{
await Task.WhenAll(task1, task2, task3);
}
catch(AggregateException ae)
{
foreach(var ex in ae.InnerExceptions)
{
Console.WriteLine($"Caught exception: {ex.Message}");
}
}
更精细的错误处理方式:
csharp复制var tasks = new[] { task1, task2, task3 };
try
{
await Task.WhenAll(tasks);
}
catch
{
// 检查每个任务的状态
foreach(var task in tasks.Where(t => t.IsFaulted))
{
Console.WriteLine($"Task failed: {task.Exception}");
}
}
3. 高级应用场景与性能优化
3.1 限制并发度
当处理大量任务时,直接使用WhenAll可能导致资源耗尽。这时需要限制并发度:
csharp复制public async Task ProcessWithThrottling(IEnumerable<Func<Task>> taskFactories, int maxConcurrency)
{
var semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = new List<Task>();
foreach(var factory in taskFactories)
{
await semaphore.WaitAsync();
tasks.Add(Task.Run(async () =>
{
try { await factory(); }
finally { semaphore.Release(); }
}));
}
await Task.WhenAll(tasks);
}
3.2 超时控制
结合Task.Delay实现超时机制:
csharp复制public async Task<string> DownloadWithTimeoutAsync(string url, TimeSpan timeout)
{
var downloadTask = HttpClient.GetStringAsync(url);
var timeoutTask = Task.Delay(timeout);
var completedTask = await Task.WhenAny(downloadTask, timeoutTask);
if(completedTask == timeoutTask)
{
throw new TimeoutException("Download timed out");
}
return await downloadTask;
}
4. 实战中的陷阱与最佳实践
4.1 避免死锁
常见的死锁场景是在UI线程或ASP.NET请求上下文中错误使用.Result或.Wait():
csharp复制// 错误示例 - 可能导致死锁
public string GetData()
{
return FetchDataAsync().Result;
}
// 正确做法
public string GetData()
{
return FetchDataAsync().GetAwaiter().GetResult(); // 仍然不理想
// 最佳方案是保持async/await一直传递到调用链顶端
}
4.2 ConfigureAwait的使用
在库代码中,通常应该使用ConfigureAwait(false)来避免不必要的上下文捕获:
csharp复制public async Task<string> GetCombinedDataAsync()
{
var data1 = await FetchData1Async().ConfigureAwait(false);
var data2 = await FetchData2Async().ConfigureAwait(false);
return data1 + data2;
}
4.3 任务取消模式
正确实现取消功能:
csharp复制public async Task LongRunningOperationAsync(CancellationToken cancellationToken)
{
for(int i = 0; i < 100; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(100, cancellationToken);
// 处理工作...
}
}
5. 性能分析与调试技巧
5.1 使用Visual Studio的并行堆栈窗口
调试异步代码时,并行堆栈窗口(Parallel Stacks)可以直观显示多个任务的调用栈状态。
5.2 诊断异步操作
使用System.Diagnostics.Activity跟踪异步操作:
csharp复制public async Task ProcessOrderAsync(Order order)
{
using var activity = new Activity("ProcessOrder").Start();
try
{
// 处理订单...
}
catch(Exception ex)
{
activity.AddTag("error", ex.Message);
throw;
}
}
5.3 性能计数器监控
监控关键性能指标:
- 线程池队列长度
- 完成的Task数量
- 活动线程数
6. 实际案例:构建高并发API客户端
让我们实现一个完整的API客户端示例:
csharp复制public class ApiClient
{
private readonly HttpClient _client;
private readonly SemaphoreSlim _rateLimiter;
public ApiClient(int maxConcurrentRequests = 10)
{
_client = new HttpClient();
_rateLimiter = new SemaphoreSlim(maxConcurrentRequests);
}
public async Task<string[]> FetchMultipleAsync(IEnumerable<string> urls,
CancellationToken cancellationToken = default)
{
var tasks = urls.Select(url => ThrottledFetchAsync(url, cancellationToken));
return await Task.WhenAll(tasks);
}
private async Task<string> ThrottledFetchAsync(string url,
CancellationToken cancellationToken)
{
await _rateLimiter.WaitAsync(cancellationToken);
try
{
var response = await _client.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
finally
{
_rateLimiter.Release();
}
}
}
这个实现包含了:
- 并发度控制
- 取消支持
- 错误处理
- 资源清理
7. 进阶模式:自定义任务组合器
我们可以创建更复杂的任务组合器来满足特定需求:
csharp复制public static async Task<T[]> WhenAllWithProgress<T>(
IEnumerable<Task<T>> tasks,
IProgress<int> progress)
{
var taskList = tasks.ToList();
var completedCount = 0;
var taskTrackers = taskList.Select(task =>
task.ContinueWith(t =>
{
Interlocked.Increment(ref completedCount);
progress?.Report(completedCount * 100 / taskList.Count);
},
TaskContinuationOptions.ExecuteSynchronously));
await Task.WhenAll(taskList);
return taskList.Select(t => t.Result).ToArray();
}
这个增强版的WhenAll会报告完成进度,适用于需要显示进度条的场合。
