引言
在现代应用程序开发中,异步编程已成为处理I/O密集型操作和提高应用程序响应能力的关键技术。C#通过async/await模式提供了一种简洁而强大的异步编程模型,使开发者能够以接近同步代码的编写方式实现高效的异步操作。本文将全面介绍C#中async/await的工作原理、最佳实践以及常见场景的应用技巧。
一、异步编程基础概念
1.1 同步 vs 异步
- 同步操作:阻塞当前线程直到操作完成
- 异步操作:不阻塞当前线程,操作完成后通过回调或通知继续执行
1.2 async/await的优势
- 代码简洁:避免了回调地狱(Callback Hell)
- 线程高效:充分利用线程池资源
- 响应性强:UI线程不会被阻塞
- 可扩展性好:适合高并发场景
二、async/await核心语法
2.1 基本结构
// 异步方法声明
public async Task<int> GetDataAsync()
{
// 异步操作
var data = await DownloadDataAsync();
// 处理结果
return ProcessData(data);
}
// 调用异步方法
async Task UseDataAsync()
{
int result = await GetDataAsync();
Console.WriteLine(result);
}
2.2 方法签名规则
- 异步方法必须返回
Task
、Task<T>
或ValueTask
/ValueTask<T>
- 方法名通常以”Async”结尾(约定而非强制)
- 使用
async
关键字修饰方法
三、底层工作原理
3.1 状态机转换
编译器将async方法转换为状态机:
- 遇到第一个await时暂停方法执行
- 保存当前上下文(同步上下文、局部变量等)
- 返回未完成的Task给调用者
- 异步操作完成后恢复执行
3.2 执行流程示例
async Task<string> FetchDataAsync()
{
Console.WriteLine("开始获取数据"); // 同步部分
string data = await httpClient.GetStringAsync(url); // 异步等待点
Console.WriteLine("数据处理"); // 异步部分
return data.ToUpper();
}
执行顺序:
- 输出”开始获取数据”
- 发起HTTP请求并返回Task
- 方法暂停,控制权返回调用者
- HTTP请求完成后,继续执行剩余代码
四、任务(Task)基础
4.1 Task类型
Task
:表示无返回值的异步操作Task<T>
:表示返回T类型结果的异步操作ValueTask
/ValueTask<T>
:轻量级任务,减少堆分配
4.2 任务控制
// 创建已完成的Task
Task completedTask = Task.CompletedTask;
Task<int> successTask = Task.FromResult(42);
// 组合任务
Task.WhenAll(task1, task2, task3); // 所有任务完成
Task.WhenAny(task1, task2, task3); // 任一任务完成
// 超时控制
await task.TimeoutAfter(TimeSpan.FromSeconds(5));
五、异常处理
5.1 捕获异步异常
try
{
await SomeAsyncOperation();
}
catch (HttpRequestException ex)
{
// 处理特定异常
}
catch (Exception ex)
{
// 处理其他异常
}
5.2 聚合异常处理
Task[] tasks = new Task[3];
try
{
await Task.WhenAll(tasks);
}
catch (Exception ex)
{
if (ex is AggregateException ae)
{
foreach (var e in ae.InnerExceptions)
{
// 处理每个任务异常
}
}
}
六、取消异步操作
6.1 CancellationToken使用
async Task LongRunningOperationAsync(CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(100, token);
}
}
// 调用方
var cts = new CancellationTokenSource();
var task = LongRunningOperationAsync(cts.Token);
// 取消操作
cts.CancelAfter(TimeSpan.FromSeconds(2));
6.2 超时控制
// 使用CancellationToken实现超时
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await SomeAsyncOperation(cts.Token);
// 扩展方法实现
public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
{
var cts = new CancellationTokenSource();
var delayTask = Task.Delay(timeout, cts.Token);
var completedTask = await Task.WhenAny(task, delayTask);
if (completedTask == delayTask)
{
throw new TimeoutException();
}
cts.Cancel();
await task; // 确保已观察到任何异常
}
七、性能优化技巧
7.1 避免常见陷阱
- async void:仅用于事件处理程序
- 阻塞异步代码:避免
.Result
或.Wait()
- 过度并行:合理控制并发度
- 虚假异步:确保方法真正异步
7.2 最佳实践
- 使用ConfigureAwait(false):库代码中避免同步上下文开销
var data = await httpClient.GetStringAsync(url).ConfigureAwait(false);
- ValueTask优化:对高频调用或可能同步完成的方法
public ValueTask<int> CacheGetAsync(string key)
{
if (cache.TryGetValue(key, out var value))
return new ValueTask<int>(value);
return new ValueTask<int>(LoadFromDbAsync(key));
}
- 批处理操作:减少上下文切换
// 不佳做法
foreach (var item in items)
{
await ProcessItemAsync(item);
}
// 优化做法
var tasks = items.Select(ProcessItemAsync);
await Task.WhenAll(tasks);
八、高级应用场景
8.1 异步流(Async Streams)
public async IAsyncEnumerable<int> GenerateSequenceAsync()
{
for (int i = 0; i < 20; i++)
{
await Task.Delay(100);
yield return i;
}
}
// 消费异步流
await foreach (var number in GenerateSequenceAsync())
{
Console.WriteLine(number);
}
8.2 异步锁
private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1);
async Task AccessSharedResourceAsync()
{
await _asyncLock.WaitAsync();
try
{
// 访问共享资源
}
finally
{
_asyncLock.Release();
}
}
九、与并行编程结合
9.1 并行异步操作
var tasks = uris.Select(uri => httpClient.GetStringAsync(uri));
string[] pages = await Task.WhenAll(tasks);
// 带限流的并行
using var semaphore = new SemaphoreSlim(10);
var tasks = uris.Select(async uri =>
{
await semaphore.WaitAsync();
try
{
return await httpClient.GetStringAsync(uri);
}
finally
{
semaphore.Release();
}
});
9.2 PLINQ与异步
var data = await Task.Run(() =>
largeCollection.AsParallel()
.Where(item => item.IsValid)
.Select(item => item.Value)
.ToList());
十、实际应用示例
10.1 文件异步读写
async Task<string> ReadFileAsync(string path)
{
using var reader = File.OpenText(path);
return await reader.ReadToEndAsync();
}
async Task WriteFileAsync(string path, string content)
{
using var writer = File.CreateText(path);
await writer.WriteAsync(content);
}
10.2 数据库异步访问
public async Task<List<Product>> GetProductsAsync(int categoryId)
{
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var command = new SqlCommand(
"SELECT * FROM Products WHERE CategoryId = @categoryId",
connection);
command.Parameters.AddWithValue("@categoryId", categoryId);
await using var reader = await command.ExecuteReaderAsync();
var products = new List<Product>();
while (await reader.ReadAsync())
{
products.Add(new Product
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Price = reader.GetDecimal(2)
});
}
return products;
}
结语
C#的async/await模式极大地简化了异步编程的复杂性,使开发者能够以直观的方式编写高效的异步代码。通过理解其底层机制、掌握最佳实践并合理应用于各种场景,您可以构建出响应迅速、资源利用率高的应用程序。
记住,异步编程不是万能的——对于CPU密集型任务,传统的多线程或并行编程可能更合适。合理评估应用场景,选择最适合的并发模型,才能真正发挥async/await的优势。随着C#语言的不断发展,异步编程功能也在持续增强,建议保持对最新特性的关注,如C# 10中的异步方法构建器改进等。