C#中有個很好用的東西,TAP異步編程(Task-based Asynchronous Pattern),是目前C#推薦的異步編程模型。它基于 System.Threading.Tasks.Task 和 async/await 關鍵字,旨在簡化異步代碼的編寫、調試和維護。TAP 是現代 .NET 應用開發中最常用、最推薦的異步編程方式。
要實現TAP,有三個基本元素:Task、async和await。
- Task: 表示一個異步操作,可能沒有返回值(
Task
)或有返回值(Task<T>
) - async: 標記一個方法為異步
- await: 用于等待一個異步操作完成,不會阻塞當前線程。
在Winform中,使用好TAP編程,可以方便的將異步回調操作變為同步,且不會阻塞UI線程! 非常優雅。
千問3給出的TAP優勢:
特性 | 說明 |
---|---|
簡化異步代碼 | 使用 async/await 避免了復雜的回調嵌套(回調地獄)。 |
線程友好 | 不會阻塞主線程(如 UI 線程),提升用戶體驗。 |
異常處理統一 | 異常可以通過 try/catch 捕獲,不需要額外的錯誤回調。 |
取消支持 | 支持通過 CancellationToken 安全取消異步操作。 |
上下文感知 | 通過 SynchronizationContext 自動恢復執行上下文(如 UI 線程) |
1.回調封裝為異步
回調的關鍵是需要有key來識別是哪次調用!
通過TaskCompletionSource來封裝回調執行時傳輸的結果數據。
通過await 一個Task實現阻塞等待。
基本代碼如下:
public static class CallBackAsync{// 存儲異步任務的上下文private static readonly ConcurrentDictionary<int, TaskCompletionSource<int>> _pendingTasks = new ConcurrentDictionary<int, TaskCompletionSource<int>>();// 回調中調用public static void OnCallBackAsync(int key, int result){if (_pendingTasks.TryRemove(key, out var tcs)){tcs.TrySetResult(result);//這里會激發await,并返回結果}}// 異步調用public static async Task<int> ChannelCtrlAsync(int key){var tcs = new TaskCompletionSource<int>();// 緩存任務上下文_pendingTasks[key] = tcs;// 調用原生同步方法int ret = NativeFunc(key);if (ret != 0){_pendingTasks.TryRemove(key, out _);tcs.TrySetException(new Exception($"{ret}")); //通過異常碼返回錯誤!}return await tcs.Task; //關鍵點}// 原生方法public static int NativeFunc(int key){Console.WriteLine("NativeFunc");//異常情況//return -1;//線程中模擬回調Task.Run(() =>{OnCallBackAsync(1, 99);});return 0;}}
這里用ConcurrentDictionary字典封裝了key和TaskCompletionSource的上下文信息,異步調用時要創建一份,回調時進行查找并刪除。
注意當原生調用立即返回錯誤且不進回調時,產生的錯誤碼沒法通過TaskCompletionSource傳出,此時可通過異常機制來輸出。
按鈕調用的實現:
private async void button1_Click(object sender, EventArgs e) //必須async標記{try{var result = await CallBackAsync.ChannelCtrlAsync(1).ConfigureAwait(true); // 恢復UI上下文//繼續干其他事情,UI不會阻塞}catch (Exception ex){//表示底層調用直接返回了錯誤碼Console.WriteLine($"底層直接返回,返回值:{ex.Message}");}}
2.阻塞調用封裝
此時無需TaskCompletionSource去手動等待,比如串口的阻塞式異步:
public async Task SendDataAsync(string strText){return await Task.Run(() =>{m_Port.Write(strText);});}
3.任務取消
通過傳入參數CancellationTokenSource 對象,可以在外部調用Cancel()方法取消任務。
public async Task SendXXXAsync(string strText, CancellationTokenSource cts){return await Task.Run(() =>{while(1){if (cts != null && cts.Token.IsCancellationRequested){return;}m_Port.Write(部分數據); //循環發送使用}});}
4.超時處理
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(3));
var completedTask = await Task.WhenAny(task, timeoutTask);
if (completedTask == timeoutTask)
{// 超時處理邏輯tcs.TrySetException(new Exception("ERR_CTRL_TIMEOUT")); //通過異常碼返回錯誤!
}
return await tcs.Task;