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()
{// Arrangevar service = new MyService();// Actvar result = await service.GetAsync();// AssertAssert.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()
{// Arrangevar service = new FaultyService();// Act & Assertawait 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
- ??監控異步性能??以識別瓶頸
- ??保持異步方法鏈??避免混合同步/異步調用