C# async await – 異步編程
理解並使用 async 和 await
C# async await 是現代軟件開發中不可或缺的一部分,特別是在處理長時間運行的操作時。async和await關鍵字使得異步編程變得更加簡單和直觀。本文旨在提供一個基礎指南,幫助開發者理解並有效地使用這些關鍵字。
本篇會分成兩部分:
C# Async 異步編程的基礎介紹
異步編程允許程序在等待長時間運行的操作完成時繼續執行其他任務。這對於改善應用程序的響應性和性能非常重要。在C#中,async和await關鍵字使得實現異步操作變得更加容易。
C# async 關鍵字
async關鍵字用於標記方法、lambda表達式或匿名方法為異步。異步方法通常返回Task或Task<T>類型,其中T是方法返回的類型。
例子:
1 2 3 4 |
public async Task DoSomethingAsync() { // 異步操作的代碼 } |
C# await 關鍵字
當你在異步方法內部呼叫另一個異步方法時,可以使用await關鍵字。這會暫停當前方法的執行,直到被呼叫的異步操作完成。這樣做的好處是,它不會阻塞調用線程。
例子:
1 2 3 4 5 |
public async Task CallAnotherAsyncMethod() { await DoSomethingAsync(); // 繼續執行後續代碼 } |
C# Async 處理異步方法的返回值
1 2 3 4 5 6 7 8 9 10 11 |
public async Task<int> CalculateValueAsync() { // 進行一些異步計算 return 42; } public async Task UseCalculatedValue() { int value = await CalculateValueAsync(); // 使用返回的值 } |
C# Async 異步方法的異常處理
異步方法中的異常可以通過在呼叫await時使用try-catch塊來捕獲。
例子:
1 2 3 4 5 6 7 8 9 10 11 |
public async Task HandleErrorsAsync() { try { await DoSomethingAsync(); } catch (Exception ex) { // 處理異常 } } |
以上是對C# async await的基本應用,接下來要開始進行實驗,關於異步操作對執行時間的影響。
實驗 – C# 中的異步編程 – async await
下方為Demo結果,在連續兩筆資料處理,總共花8秒多;而平行處理兩筆資料只需4秒多,可見平行處理事宜,可大幅減少處理時間。但是,也取決於處理的資料量,在圖3中,可見串列處理相較於平行處理還來的快;那這是什麼原因呢?
處理的資料量,也就是在Thread Pool啟用Thread所花費的時間,遠大於資料處理的時間,才造成如此結果。
如何取得回傳值,這裡跟一般函數相似,只不過須透過Task<T>泛型來取得;與傳統Thread類不同,想取得回傳值得建立全域變數同步並取得,不僅會使代碼混亂,且會不好維護。
程式碼範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
static async Task DataHandlerDemo() { Action<int> func = Func_demo2; Stopwatch stopwatch = new Stopwatch(); Console.WriteLine("Serial Data handler Start----------------"); stopwatch.Restart(); func(1); func(2); stopwatch.Stop(); Console.WriteLine($"Serial Data handler Spend[{stopwatch.ElapsedMilliseconds} ms]"); Console.WriteLine("Serial Data handler End------------------"); Console.WriteLine(); Console.WriteLine(); Console.WriteLine("Parallel Data handler Start--------------"); stopwatch.Restart(); Task func1 = Task.Run(() => func(1)); func(2); await func1; stopwatch.Stop(); Console.WriteLine($"Parallel Data handler Spend[{stopwatch.ElapsedMilliseconds} ms]"); Console.WriteLine("Parallel Data handler End----------------"); } |
上述代碼演示了在C#中使用異步編程處理數據的兩種方式:串行(Serial)和並行(Parallel)處理。
- 串行處理:
- 定義一個
Action<int>
委託func
,並將其關聯到Func_demo2
方法。 - 使用
Stopwatch
計時器測量執行時間。 - 首先以串行方式調用
func
兩次,傳入不同的參數(1和2)。 - 計時器記錄並輸出這兩個函數調用的總時間。
- 定義一個
- 並行處理:
- 再次使用
Stopwatch
計時器測量執行時間。 - 這次,第一個
func(1)
的調用是在一個新的異步任務Task
中進行的,而func(2)
則是在當前線程中同步調用。 - 通過
await func1
,等待第一個任務完成。 - 最後,計時器記錄並輸出兩個函數並行調用的總時間。
- 再次使用
1 2 3 4 5 6 7 8 9 10 11 |
static void Func_demo1(int funcNum) { ulong i = int.MaxValue; ulong result = 0; do { result += i; --i; } while (i != 0); Console.WriteLine($"Func[{funcNum}] Done"); } |
1 2 3 4 5 6 7 8 9 10 11 |
static void Func_demo2(int funcNum) { int i = ushort.MaxValue; int result = 0; do { result += i; --i; } while (i != 0); Console.WriteLine($"Func[{funcNum}] Done"); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
static async Task returnValueHandlerDemo() { var value = await Task.Run(() => returnValueFunc_demo()); Console.WriteLine(); Console.WriteLine(); Console.WriteLine($"returnValueHandlerDemo - return value = [{value}]"); } static ulong returnValueFunc_demo() { ulong i = int.MaxValue; ulong result = 0; do { result += i; --i; } while (i != 0); return result; } |
透過Await、Async來取代傳統Thread是很推薦的作法,以下是一些分析和總結:
優勢和好處:
- 非阻塞程式碼:await和async使程式碼能夠以非阻塞方式執行,這樣主執行緒不會被封鎖,並且可以繼續處理其他工作。這有助於提高應用程式的反應性和效能。
- 資源效能:相對於傳統多執行緒,使用異步程式設計可以更有效地使用系統資源,因為不需要維護多個執行緒的上下文切換成本。
- 簡化程式碼:異步程式設計使程式碼更具可讀性和簡潔性,避免了多執行緒編程所需的複雜同步和共享資源管理。
- 例外處理:await和async模式使例外處理變得更簡單,並且異常可以更容易地傳播到適當的處理程式。
注意事項和挑戰:
- 適用性:await和async適合處理I/O密集型操作,如網絡請求或檔案存取。對於CPU密集型操作,使用多執行緒可能更為適合。
- 學習曲線:異步程式設計有一定的學習曲線,開發者需要熟悉異步程式設計模式和異步操作的管理。
- 執行序:使用await和async不一定保證執行順序,因此在需要確保特定順序執行的情況下,需謹慎設計。
- 性能調校:需要仔細調整異步操作以避免過多的執行序切換,這可能會導致性能下降。
結論
總結來說,await和async的使用在異步程式設計中具有明顯的好處,特別是在處理I/O密集型操作時。然而,它不適用於所有情況,且需要開發者謹慎選擇適當的使用方式,避免消耗不必要的時間或資源。在大多數現代程式設計中,await和async已經成為提高效能和可讀性的有力選項。