C# 异步编程详解
一、异步编程基础概念
1. 同步 vs 异步
- 同步(Synchronous):任务按顺序执行,前一个任务完成后才会执行下一个
- 异步(Asynchronous):任务可以非阻塞地启动,主线程可以继续执行其他操作
2. 异步编程的优势
- 提高应用程序响应能力(特别是UI应用)
- 更好地利用系统资源
- 避免线程阻塞
- 提高吞吐量
二、C#异步编程模型
1. async/await关键字
public async Task<string> GetDataAsync()
{
// 模拟耗时操作
await Task.Delay(1000);
return "Data loaded";
}
// 使用
var data = await GetDataAsync();
Console.WriteLine(data);
关键点:
async
修饰方法,表示该方法包含异步操作await
关键字用于等待异步操作完成- 异步方法返回
Task
或Task<T>
2. Task和Task
// 返回void的异步方法(仅用于事件处理)
public async void HandleButtonClick()
{
await SomeAsyncOperation();
}
// 返回Task的异步方法
public async Task ProcessDataAsync()
{
await File.ReadAllTextAsync("data.txt");
}
// 返回Task<T>的异步方法
public async Task<int> CalculateSumAsync(IEnumerable<int> numbers)
{
return await Task.Run(() => numbers.Sum());
}
Task状态:
Created
:已创建但未启动WaitingForActivation
:等待激活WaitingToRun
:等待运行Running
:正在运行RanToCompletion
:已完成Canceled
:已取消Faulted
:出错
三、异步方法实现方式
1. 基于I/O的异步操作
// 文件I/O
public async Task ReadFileAsync(string path)
{
using (var reader = File.OpenText(path))
{
var content = await reader.ReadToEndAsync();
Console.WriteLine(content);
}
}
// 网络I/O
public async Task DownloadDataAsync(string url)
{
using (var client = new HttpClient())
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
}
2. 基于CPU的异步操作
// 使用Task.Run将CPU密集型工作转移到线程池
public async Task ProcessDataAsync(IEnumerable<int> data)
{
var result = await Task.Run(() =>
{
// CPU密集型计算
return data.Sum(x => x * x);
});
Console.WriteLine($"Sum: {result}");
}
注意:对于真正的并行计算,考虑使用Parallel.For
或PLINQ
3. 组合多个异步操作
// 等待多个任务完成
public async Task ProcessMultipleAsync()
{
var task1 = GetDataAsync();
var task2 = GetOtherDataAsync();
// 等待所有完成
await Task.WhenAll(task1, task2);
Console.WriteLine($"Data1: {task1.Result}, Data2: {task2.Result}");
}
// 等待任意一个完成
public async Task ProcessAnyAsync()
{
var task1 = GetDataAsync();
var task2 = GetOtherDataAsync();
var completedTask = await Task.WhenAny(task1, task2);
Console.WriteLine($"Completed: {completedTask == task1 ? "Task1" : "Task2"}");
}
四、异步编程最佳实践
1. 避免async void
// 不推荐 - 无法捕获异常
public async void DangerousMethod()
{
throw new Exception("Oops!");
}
// 推荐 - 可以await或等待
public async Task SafeMethodAsync()
{
await Task.Delay(100);
throw new Exception("Oops!");
}
例外:事件处理程序可以使用async void
2. 异常处理
public async Task HandleExceptionsAsync()
{
try
{
await SomeAsyncOperation();
}
catch (Exception ex)
{
// 处理异常
Console.WriteLine($"Error: {ex.Message}");
}
}
// 多个任务的异常处理
public async Task HandleMultipleExceptionsAsync()
{
try
{
await Task.WhenAll(
Operation1(),
Operation2(),
Operation3());
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
3. 取消异步操作
// 使用CancellationToken
public async Task LongRunningOperationAsync(CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(100, token);
Console.WriteLine($"Step {i}");
}
}
// 使用示例
var cts = new CancellationTokenSource();
try
{
await LongRunningOperationAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was canceled");
}
// 取消操作
cts.Cancel();
4. 异步方法设计原则
- 返回Task而非void(除非是事件处理程序)
- 避免在异步方法中使用Result或Wait()(可能导致死锁)
- 保持异步方法链(避免混合同步和异步代码)
- 考虑异步方法的粒度(不要过度拆分)
五、异步与并行编程
1. 异步 vs 并行
特性 | 异步编程 | 并行编程 |
---|---|---|
目标 | 非阻塞执行 | 同时执行多个任务 |
线程使用 | 通常不创建新线程 | 使用多个线程 |
适用场景 | I/O密集型操作 | CPU密集型操作 |
关键字 | async/await | Parallel.For/ForEach |
2. 混合使用示例
public async Task ProcessDataAsync(IEnumerable<Data> data)
{
// 并行处理数据项
var processedData = data.AsParallel()
.Select(d => ProcessItem(d))
.ToList();
// 异步保存结果
await SaveResultsAsync(processedData);
}
private Data ProcessItem(Data d)
{
// CPU密集型处理
return new Data { /* ... */ };
}
private async Task SaveResultsAsync(IEnumerable<Data> data)
{
// 异步保存到数据库
await _dbContext.BulkInsertAsync(data);
}
六、异步编程中的常见问题
1. 死锁问题
错误示例:
public async Task DeadlockExample()
{
await Task.Run(async () =>
{
// 在UI线程上调用Wait()会导致死锁
await SomeAsyncOperation().ConfigureAwait(false); // 解决方案
}).Wait(); // 阻塞调用
}
解决方案:
- 使用
ConfigureAwait(false)
(非UI上下文) - 避免在异步方法中调用
.Result
或.Wait()
- 使用
await
而不是Task.Run
包装异步操作
2. 上下文保留问题
// 默认情况下,await会捕获当前上下文(如UI线程)
public async Task UpdateUIAsync()
{
var data = await GetDataAsync();
// 自动回到UI线程
textBox.Text = data;
}
// 如果不需要回到原始上下文
public async Task ProcessInBackgroundAsync()
{
var data = await GetDataAsync().ConfigureAwait(false);
// 不会回到原始上下文
ProcessData(data);
}
3. 性能优化
避免过度异步化:
// 不必要的异步包装
public async Task UnnecessaryAsync()
{
await Task.Run(() =>
{
// 同步操作
Thread.Sleep(1000);
});
}
正确做法:
- 只对真正的I/O操作使用异步
- 对CPU密集型操作考虑并行处理
- 避免在热路径上使用异步(如游戏循环)
七、高级异步模式
1. 异步流(IAsyncEnumerable)
// 生成异步流
public async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(100); // 模拟异步操作
yield return i;
}
}
// 使用异步流
await foreach (var number in GenerateNumbersAsync(10))
{
Console.WriteLine(number);
}
2. 异步锁
private readonly SemaphoreSlim _semaphore = new(1, 1);
public async Task ProtectedOperationAsync()
{
await _semaphore.WaitAsync();
try
{
// 受保护的代码
}
finally
{
_semaphore.Release();
}
}
3. 异步工厂模式
public interface IAsyncFactory<T>
{
Task<T> CreateAsync();
}
public class AsyncDataFactory : IAsyncFactory<Data>
{
public async Task<Data> CreateAsync()
{
await Task.Delay(100); // 模拟异步初始化
return new Data();
}
}
八、异步测试
1. 单元测试异步方法
[Fact]
public async Task TestAsyncMethod()
{
// Arrange
var service = new MyService();
// Act
var result = await service.GetAsync();
// Assert
Assert.NotNull(result);
}
// 使用Moq测试异步方法
[Fact]
public async Task TestWithMock()
{
var mock = new Mock<IRepository>();
mock.Setup(r => r.GetDataAsync())
.ReturnsAsync(new Data { Id = 1 });
var service = new MyService(mock.Object);
var result = await service.GetData();
Assert.Equal(1, result.Id);
}
2. 测试异步代码中的异常
[Fact]
public async Task TestException()
{
// Arrange
var service = new FaultyService();
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(
() => service.FaultyOperationAsync());
}
九、异步性能监控
1. 使用DiagnosticSource
// 在异步方法中添加诊断事件
private static readonly DiagnosticSource _diagnosticSource =
new DiagnosticListener("MyAsyncComponent");
public async Task ProcessAsync()
{
if (_diagnosticSource.IsEnabled("StartProcess"))
{
_diagnosticSource.Write("StartProcess", new { Timestamp = DateTime.UtcNow });
}
await Task.Delay(100);
if (_diagnosticSource.IsEnabled("EndProcess"))
{
_diagnosticSource.Write("EndProcess", new { Duration = 100 });
}
}
2. 使用AsyncLocal跟踪上下文
private static readonly AsyncLocal<string> _context = new();
public async Task OperationWithContext()
{
_context.Value = "OperationStarted";
await Task.Delay(100);
Console.WriteLine(_context.Value); // 仍然可以访问
}
十、异步编程的未来发展
1. C#中的新特性
- C# 8.0:IAsyncEnumerable
- C# 9.0:异步流改进
- C# 10.0:更高效的异步方法生成
2. .NET中的改进
- .NET Core 3.0+:更好的异步IO性能
- .NET 5+:统一的异步API
- .NET 6+:更智能的异步调度器
十一、最佳实践总结
- 优先使用async/await而非ContinueWith或Task.Result
- 在UI应用中,确保异步操作回到UI线程(使用ConfigureAwait(false)谨慎)
- 避免混合同步和异步代码(如Wait()和Result)
- 为长时间运行的操作使用CancellationToken
- 考虑异步流处理连续数据(如日志、传感器数据)
- 测试异步代码时使用AsyncTestMethods
- 监控异步性能以识别瓶颈
- 保持异步方法链避免混合同步/异步调用