https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index翻譯web
1. 引入編程
Task異步編程模型(TAP)提供了對異步代碼的抽象,將代碼做爲語句序列,能夠在每一個階段完成下個階段開始前讀取代碼,該過程當中,編譯器進行了屢次轉換,由於一些語句可能啓動工做並返回正在進行的工做任務。異步
Task異步編程的目標就是,啓動相似於語句序列的代碼,但當任務執行完成時,基於外部資源分配以一個更復雜的順序執行任務,相似於人們如何爲包含異步任務的進程發出指令。async
2. 異步編程ide
在本文中,經過一個製做早餐的示例,瞭解關鍵字async和await關鍵字如何使得包含一系列異步指令的操做更容易。異步編程
製造早餐的列表以下:微服務
(1)倒一杯咖啡;ui
(2)將鍋加熱,而後煎兩個雞蛋;spa
(3)炒三片培根;翻譯
(4)吐司兩片面包;
(5)加入黃油和果醬吐司;
(6)倒一杯橙汁
烹飪早餐是異步工做的一個很好範例,同一我的能夠在一個步驟完成以前去執行另外一個步驟。該操做的同步代碼簡易版以下:
static void Main(string[] args) { Coffee cup = PourCoffee(); Console.WriteLine("coffee is ready"); Egg eggs = FryEggs(2); Console.WriteLine("eggs are ready"); Bacon bacon = FryBacon(3); Console.WriteLine("bacon is ready"); Toast toast = ToastBread(2); ApplyButter(toast); ApplyJam(toast); Console.WriteLine("toast is ready"); Juice oj = PourOJ(); Console.WriteLine("oj is ready"); Console.WriteLine("Breakfast is ready!"); }
若是採用上述給出的步驟進行早餐準備,整個效率會很是低下,而事實上,咱們能夠在鍋加熱煎雞蛋的過程當中,炒培根,在培根開始以後,就能夠將麪包放入烤麪包機。要想實現動做的異步執行,須要編寫異步代碼。異步實現的簡易代碼以下:
static async void Main(string[] args) { Coffee cup = PourCoffee(); Console.WriteLine("coffee is ready"); Egg eggs =await FryEggs(2); Console.WriteLine("eggs are ready"); Bacon bacon =await FryBacon(3); Console.WriteLine("bacon is ready"); Toast toast =await ToastBread(2); ApplyButter(toast); ApplyJam(toast); Console.WriteLine("toast is ready"); Juice oj = PourOJ(); Console.WriteLine("oj is ready"); Console.WriteLine("Breakfast is ready!"); }
此時,煎雞蛋、炒培根和烤麪包這三個動做就不須要依次執行,當烹飪雞蛋或培根時,代碼不會阻止,能夠同時啓動多個組件任務。
2.1 同時啓動任務
許多狀況下,咱們但願當即啓動多個獨立任務,而後,當每一個任務完成後,能夠繼續其餘已準備好的工做。在上述早餐實例中,也就是要求更快的完成早餐。.NET Core中,System.Threading.Tasks.Task和相關類能夠用來推理正在進行的任務類,該特性使得更容易編寫接近實際建立早餐方式的代碼。可以同時開始烹飪雞蛋、培根和吐司。當每一個動做須要執行時,咱們能夠把注意力轉移到該任務上,注意下一個動做,而後等待其餘須要注意的事情。
咱們能夠啓動一個任務並保留該工做的Task對象,await在處理結果以前,咱們將完成每項任務。對上述建立早餐的代碼進行修改,第一步是在操做開始時存儲操做,而非等待它們。
Coffee cup = PourCoffee(); Console.WriteLine("coffee is ready"); Task<Egg> eggTask=FryEggs(2); Egg eggs=await eggTask; Console.WriteLine("eggs are ready"); Task<Bacon> baconTask=FryBacon(3); Bacon bacon=await baconTask; Console.WriteLine("bacon is ready"); Task<Toast> toastTask=ToastBread(2); Toast toast=await toastTask; ApplyButter(toast); ApplyJam(toast); Console.WriteLine("toast is ready"); Juice oj = PourOJ(); Console.WriteLine("oj is ready"); Console.WriteLine("Breakfast is ready!");
接下來,能夠將await在提供早餐前將炒培根和煎雞蛋語句移至末尾,代碼以下:
Coffee cup = PourCoffee(); Console.WriteLine("coffee is ready"); Task<Egg> eggTask = FryEggs(2); Task<Bacon> baconTask = FryBacon(3); Task<Toast> toastTask = ToastBread(2); Toast toast = await toastTask; ApplyButter(toast); ApplyJam(toast); Console.WriteLine("toast is ready"); Juice oj = PourOJ(); Console.WriteLine("oj is ready"); Egg eggs = await eggTask; Console.WriteLine("eggs are ready"); Task<Bacon> baconTask = FryBacon(3); Bacon bacon = await baconTask; Console.WriteLine("bacon is ready");
該代碼的效果更好,能夠當即啓動全部的異步任務,只有在須要結果時纔等待每項任務。該代碼的實現相似於web應用程序中的代碼,可以發出不一樣微服務的請求,而後將結果組合成單個頁面。此時,咱們將當即發出全部的請求,而後await全部的任務並組合成web頁面。
2.2 任務組合
上述製做早餐的過程當中,製做吐司是異步操做(烤麪包)和同步操做(添加黃油和果醬)的組合。此時,咱們須要知道,異步操做和後續同步操做的組合是異步操做,即若是操做的任意部分是異步的,則整個操做都是異步的。
下面給出建立工做組合的方法。在供應早餐以前,若是想要在添加黃油和果醬以前等待烘烤麪包的任何,則可使用如下代碼表示:
async Task<Toast> makeToastWithButterAndJamAsync(int number){ var plainToast=await ToastBreadAsync(number); ApplyButter(plainToast); ApplyJsm(plainToast); return plainToast; }
上述方法中包含了一個await語句,包含異步操做,該方法表明了烘烤麪包的任務,而後添加黃油和果醬,以後返回一個Task<TResult>,表示這三個操做的組合結果。當前代碼課修改成:
static async Task Main(string[] args){ Coffee cup = PourCoffee(); Console.WriteLine("coffee is ready"); var eggsTask = FryEggsAsync(2); var baconTask = FryBaconAsync(3); var toastTask = makeToastWithButterAndJamAsync(2); var eggs = await eggsTask; Console.WriteLine("eggs are ready"); var bacon = await baconTask; Console.WriteLine("bacon is ready"); var toast = await toastTask; Console.WriteLine("toast is ready"); Juice oj = PourOJ(); Console.WriteLine("oj is ready"); Console.WriteLine("Breakfast is ready!"); async Task<Toast> makeToastWithButterAndJamAsync(int number) { var plainToast = await ToastBreadAsync(number); ApplyButter(plainToast); ApplyJam(plainToast); return plainToast; } }
以上代碼的修改說明了異步代碼工做的重要性,經過將操做分離爲返回任務的新方法來組合任務,能夠選擇什麼時候等待這項任務,同時啓動其餘任務
2.3 有效地等待其餘任務
await能夠經過使用Task類的方法來該井前面代碼末尾的一系列語句,其中一個API是WhenAll,它返回一個在其參數列表中全部任務完成時完成的Task,如如下代碼所示:
await Task.WhenAll(eggTask,baconTask,toastTask); Console.WriteLine("eggs are ready"); Console.WriteLine("bacon is ready"); Console.WriteLine("toast is ready"); Console.WriteLine("Breakfast is ready!");
另外一個選擇是使用WhenAny,用它修飾的任務在任何參數完成時都返回一個Task<Task>,咱們在知道任務已經完成時,能夠等待返回的結果。如下代碼顯示瞭如何使用WhenAny等待第一個任務完成而後處理其結果,處理完結果後,從傳遞給的任務列表中刪除該已完成的任務。
var allTasks=new List<Task>{aggsTask,baconTask,toastTask}; while(allTask.Any()){ Task finished=await Task.WhenAny(allTasks); if (finished == eggsTask) { Console.WriteLine("eggs are ready"); allTasks.Remove(eggsTask); var eggs = await eggsTask; } else if (finished == baconTask) { Console.WriteLine("bacon is ready"); allTasks.Remove(baconTask); var bacon = await baconTask; } else if (finished == toastTask) { Console.WriteLine("toast is ready"); allTasks.Remove(toastTask); var toast = await toastTask; } else allTasks.Remove(finished); } Console.WriteLine("Breakfast is ready!");
在全部更改後,最終版本main方法以下:
static async Task Main(string[] args) { Coffee cup = PourCoffee(); Console.WriteLine("coffee is ready"); var eggsTask = FryEggsAsync(2); var baconTask = FryBaconAsync(3); var toastTask = makeToastWithButterAndJamAsync(2); var allTasks = new List<Task>{eggsTask, baconTask, toastTask}; while(allTask.Any()){ Task finished = await Task.WhenAny(allTasks); if (finished == eggsTask) { Console.WriteLine("eggs are ready"); allTasks.Remove(eggsTask); var eggs = await eggsTask; } else if (finished == baconTask) { Console.WriteLine("bacon is ready"); allTasks.Remove(baconTask); var bacon = await baconTask; } else if (finished == toastTask) { Console.WriteLine("toast is ready"); allTasks.Remove(toastTask); var toast = await toastTask; } else allTasks.Remove(finished); } Console.WriteLine("Breakfast is ready!"); async Task<Toast> makeToastWithButterAndJamAsync(int number) { var plainToast = await ToastBreadAsync(number); ApplyButter(plainToast); ApplyJam(plainToast); return plainToast; } }