1. 源起
在我們從 .Net 轉 .Net Core 的過程中,我們通常會查看一些源碼或者看書進行學習,在其中你可能會看到類似於這樣的代碼
1 // 1. Asp.Net Core 的 控制器 類 2 [HttpGet] 3 public async Task<IActionResult> Index() 4 { 5 ...... 6 await ...... 7 ...... 8 } 9 10 // 2. Console 的 Program.cs 文件 11 static async Task Main(string[] args) 12 { 13 ...... 14 await ...... 15 ...... 16 }
這是一個很重要的 “新特性”---異步編程。在 C# 里面異步編程是通過關鍵字 async 和 await 兩個關鍵字實現的。
那什么是異步編程?async、await是怎么樣實現異步編程的呢?
2. 為什么要異步編程
在介紹異步編程之前,我們先說一下同步編程。
通常我們編寫的代碼在被執行時,通常是自上而下一行一行的執行,如過是執行方法,則進入方法內部繼續一行一行執行。
這就是同步執行,也稱之為同步編程(也可以說是編程)。
你會發現同步編程有一個問題,就是但是當一個方法執行耗時比較長時(例如讀取文件內容),會阻塞下面的代碼執行,整個軟件都會進入等待狀態(如是GUI界面,則執行操作的線程會阻塞,整個操作界面也會處於等待狀態),體驗度相當的不好---尤其是在計算機有多核的情況下,完全可以在另一個CPU上干其他的工作,同時計算機完成耗時任務的時候通知你。這就是異步編程的起源。更多示例
3. C# 異步編程
通過使用異步編程,你可以避免性能瓶頸並增強應用程序的總體響應能力。 但是,編寫異步應用程序的傳統技術可能比較復雜,使它們難以編寫、調試和維護。
雖然 .Net 中有幾種異步編程模式,但是目前建議使用的只有一種(TAP),我們將對歷史有一些簡單的說明,對於EAP進行更深入的介紹。
3.1 .Net 提供了3中異步編程模型(更多介紹)
- TAP(基於任務的異步模式):使用單一方法表示異步操作的開始和結束,是.Net中進行異步編程的推薦方法。(.Net Framework 4中引入的)
- EAP(基於事件的異步模式):提供異步行為的基於事件的舊模型。 這種模式需要后綴為 Async 的方法,以及一個或多個事件、事件處理程序委托類型和 EventArg 派生類型。 EAP 是在 .NET Framework 2.0 中引入的。 不建議新的開發使用此模式。
- APM(異步編程模型):(也稱為 IAsyncResult 模式),這是使用 IAsyncResult 接口提供異步行為的舊模型。 在這種模式下,同步操作需要 Begin 和 End 方法(例如,BeginWrite 和 EndWrite以實現異步寫入操作)。 不建議新的開發使用此模式。
3.2 TAP(基於任務的異步模式)
C# 擁有語言級別的異步編程模型,它遵循基於任務的異步模式。
C# 異步編程的核心是 Task 和 Task<T> 對象(深入了解Task和Task<T>),這兩個對象對異步操作建模。它們受關鍵字 async 和 await 的支持(二者是異步編程的核心關鍵字)。通過這兩個關鍵字,可以使用 .NET Framework、.NET Core 或 Windows 運行時中的資源,輕松創建異步方法(幾乎與創建同步方法一樣輕松)。 使用 async 關鍵字定義的異步方法簡稱為“異步方法”。await 關鍵字控制執行 await 的方法的調用方,且它最終允許 UI 具有響應性或服務具有靈活性。
4. 異步編程 示例
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 namespace ConsoleApp 6 { 7 class Program 8 { 9 static async Task Main(string[] args) 10 { 11 try 12 { 13 //線程池,最大和最小數量(CompletionPortThreads 異步I/O線程數量) 14 //相關資料:https://docs.microsoft.com/zh-cn/dotnet/standard/threading/the-managed-thread-pool 15 ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads); //6 6 16 ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads); //32767 1000 17 18 ShowMsg(minWorkerThreads.ToString()); 19 ShowMsg(minCompletionPortThreads.ToString()); 20 ShowMsg(maxWorkerThreads.ToString()); 21 ShowMsg(maxCompletionPortThreads.ToString()); 22 23 #region 【1. 使用示例】 24 ////測試async和await(運行在ThreadPool) 25 ////相關資料:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model 26 //ShowMsg($"【Main start 1】"); 27 //Test1Async(); //異步執行,但是不知道什么時候執行 28 //Test11Async(); //異步執行,但是不知道什么時候執行 29 //ShowMsg($"【Main end 1】"); 30 //ShowMsg($"【Main start 2】"); 31 //await Test2Async(); //同步執行,等待任務執行完成 32 //ShowMsg($"【Main end 2】"); 33 34 //ShowMsg($"【Main start 3】"); 35 //Task<String> task = Test3Async(); //有返回值的方法,異步執行,但是不知道什么時候執行 36 //ShowMsg($"【Main end Task】"); 37 //ShowMsg($"{task.Result}"); //等待異步操作執行完成之后,獲取執行結果(相當於await) 38 39 //ShowMsg($"【Main end 3】"); 40 41 //TaskRun(); 42 43 //ShowMsg($"【Used samples end】"); 44 45 #endregion 46 47 #region 【2. 錯誤捕獲示例】 48 ////相關資料:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/try-catch#async-method-example 49 //// sample 1 50 //await Exception1Async(); 51 52 //// sample 2 53 //Task<String> exceptionSample1 = Exception1Async(); 54 //exceptionSample1.Wait(); 55 56 //// sample 3 57 //Console.WriteLine(exceptionSample1.Result); 58 59 //// sample 4 60 //Exception1Async(); 61 62 // 結論: 63 // 以上四種示例,前三種會進入外層try-catch,第四種則不會(因為是異步執行,不需要等待回傳結果,需要自行處理報錯信息) 64 65 //if (null != exceptionSample1.Exception) 66 //{ 67 //} 68 #endregion 69 70 } 71 catch (Exception ex) 72 { 73 Console.WriteLine(ex); 74 } 75 76 ShowMsg("The End!"); 77 } 78 79 static async Task Test1Async() 80 { 81 ShowMsg($"Test start 1"); 82 await Task.Delay(300); 83 ShowMsg($"Test end 1"); 84 } 85 86 static async Task Test11Async() 87 { 88 ShowMsg($"Test start 1111"); 89 await Task.Delay(300); 90 ShowMsg($"Test end 1111"); 91 } 92 93 static async Task Test2Async() 94 { 95 ShowMsg($"Test start 2"); 96 await Task.Delay(300); 97 ShowMsg($"Test end 2"); 98 } 99 100 static async Task<String> Test3Async() 101 { 102 ShowMsg($"Test start 3"); 103 await Task.Delay(300); 104 ShowMsg($"Test end 3"); 105 return "123"; 106 } 107 108 static void ShowMsg(String msg) 109 { 110 Console.WriteLine($" Time:{ DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") } . Message: {msg}"); 111 } 112 113 static async Task<String> Exception1Async() 114 { 115 await Task.Delay(100); 116 117 // Uncomment each of the following lines to 118 // demonstrate exception handling. 119 120 throw new OperationCanceledException("canceled"); 121 throw new Exception("Something happened."); 122 return "Done"; 123 } 124 125 static async Task TaskRun() 126 { 127 await Task.Run(() => 128 { 129 Thread.Sleep(100); 130 ShowMsg("This is task run method."); 131 }); 132 } 133 134 } 135 }
附錄
異步編程
https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Concepts
異步編程模式
https://docs.microsoft.com/zh-cn/dotnet/standard/asynchronous-programming-patterns/
C# 異步編程
https://docs.microsoft.com/zh-cn/dotnet/csharp/async
使用 Async 和 Await 的異步編程
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/
異步編程模型
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model
try-catch(C# 參考)
https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/try-catch
托管線程池
https://docs.microsoft.com/zh-cn/dotnet/standard/threading/the-managed-thread-pool
深入了解異步
https://docs.microsoft.com/zh-cn/dotnet/standard/async-in-depth
基於任務的異步模式
https://docs.microsoft.com/zh-cn/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap
任務並行庫
https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-programming/task-parallel-library-tpl