您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“C#通過同步和異步如何實現(xiàn)優(yōu)化做早餐的時間”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
一天之計在于晨,每天的早餐也是必不可少,但是很多人為了節(jié)約時間,都是簡單的吃點湊合一下或干脆不吃早餐,這對于個人身體和工作效率來說,無疑是不合理的,那么要如何做一頓早餐呢?如何能節(jié)約做早餐的時間呢?本文以一個簡單的小例子,簡述如何做一頓早餐及如何優(yōu)化做早餐的時間。僅供學(xué)習(xí)分享使用,如有不足之處,還請指正。
正常情況下,做早餐可以分為以下幾個步驟:
倒一杯咖啡。
加熱平底鍋,然后煎兩個雞蛋。
煎三片培根。
烤兩片面包。
在烤面包上加黃油和果醬。
倒一杯橙汁。
根據(jù)以上步驟進行編程,做一份早餐需要編寫程序如下:
/// <summary> /// 同步做早餐 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnBreakfast_Click(object sender, EventArgs e) { this.txtInfo.Clear(); Stopwatch watch = Stopwatch.StartNew(); watch.Start(); //1. 倒一杯咖啡。 string cup = PourCoffee(); PrintInfo("咖啡沖好了"); //2. 加熱平底鍋,然后煎兩個雞蛋。 string eggs = FryEggs(2); PrintInfo("雞蛋煎好了"); //3. 煎三片培根。 string bacon = FryBacon(3); PrintInfo("培根煎好了"); //4. 烤兩片面包。 string toast = ToastBread(2); //5. 在烤面包上加黃油和果醬。 ApplyButter(toast); ApplyJam(toast); PrintInfo("面包烤好了"); //6. 倒一杯橙汁。 string oj = PourOJ(); PrintInfo("橙汁倒好了"); PrintInfo("早餐準(zhǔn)備完畢!"); watch.Stop(); TimeSpan time = watch.Elapsed; PrintInfo(string.Format("總運行時間為:{0}秒", time.TotalSeconds.ToString("0.00"))); } /// <summary> /// 倒一杯咖啡 /// </summary> /// <returns></returns> private string PourCoffee() { PrintInfo("正在沖咖啡..."); return "咖啡"; } /// <summary> /// 抹果醬 /// </summary> /// <param name="toast"></param> private void ApplyJam(string toast) => PrintInfo("往面包抹果醬"); /// <summary> /// 抹黃油 /// </summary> /// <param name="toast"></param> private void ApplyButter(string toast) => PrintInfo("往面包抹黃油"); /// <summary> /// 烤面包 /// </summary> /// <param name="slices"></param> /// <returns></returns> private string ToastBread(int slices) { for (int slice = 0; slice < slices; slice++) { PrintInfo("往烤箱里面放面包"); } PrintInfo("開始烤..."); Task.Delay(3000).Wait(); PrintInfo("從烤箱取出面包"); return "烤面包"; } /// <summary> /// 煎培根 /// </summary> /// <param name="slices"></param> /// <returns></returns> private string FryBacon(int slices) { PrintInfo($"放 {slices} 片培根在平底鍋"); PrintInfo("煎第一片培根..."); Task.Delay(3000).Wait(); for (int slice = 0; slice < slices; slice++) { PrintInfo("翻轉(zhuǎn)培根"); } PrintInfo("煎第二片培根..."); Task.Delay(3000).Wait(); PrintInfo("把培根放盤子里"); return "煎培根"; } /// <summary> /// 煎雞蛋 /// </summary> /// <param name="howMany"></param> /// <returns></returns> private string FryEggs(int howMany) { PrintInfo("加熱平底鍋..."); Task.Delay(3000).Wait(); PrintInfo($"磕開 {howMany} 個雞蛋"); PrintInfo("煎雞蛋 ..."); Task.Delay(3000).Wait(); PrintInfo("雞蛋放盤子里"); return "煎雞蛋"; } /// <summary> /// 倒橙汁 /// </summary> /// <returns></returns> private string PourOJ() { PrintInfo("倒一杯橙汁"); return "橙汁"; }
通過運行示例,發(fā)現(xiàn)采用同步方式進行編程,做一份早餐,共計15秒鐘,且在此15秒鐘時間內(nèi),程序處于【卡住】狀態(tài),無法進行其他操作。如下所示:
同步方式做早餐,就是一個做完,再進行下一個,順序執(zhí)行,如下所示:
因為在程序進程中,會有一個主線程,用于響應(yīng)用戶的操作,同步方式下,做早餐的和前端頁面同在主線程中,所以當(dāng)開始做早餐時,就不能響應(yīng)其他的操作了。這就是【兩耳不聞窗外事,一心只讀圣賢書】的境界。但如果讓用戶長時間處于等待狀態(tài),會讓用戶體驗很不友好。比如,劉玄德三顧茅廬,大雪紛飛之下,諸葛亮在草廬中午睡,劉關(guān)張在大雪中靜等。試問有幾人會有玄德的耐心,何況程序也不是諸葛亮,用戶也沒有玄德的耐心!
上述代碼演示了不正確的實踐:構(gòu)造同步代碼來執(zhí)行異步操作。 顧名思義,此代碼將阻止執(zhí)行這段代碼的線程執(zhí)行任何其他操作。 在任何任務(wù)進行過程中,此代碼也不會被中斷。 就如同你將面包放進烤面包機后盯著此烤面包機一樣。 你會無視任何跟你說話的人,直到面包彈出。如何做才能避免線程阻塞呢?答案就是異步。 await
關(guān)鍵字提供了一種非阻塞方式來啟動任務(wù),然后在此任務(wù)完成時繼續(xù)執(zhí)行。
首先更新代碼,對于耗時的程序,采用異步方式做早餐,如下所示:
private async void btnBreakfastAsync_Click(object sender, EventArgs e) { this.txtInfo.Clear(); Stopwatch watch = Stopwatch.StartNew(); watch.Start(); //1. 倒一杯咖啡。 string cup = PourCoffee(); PrintInfo("咖啡沖好了"); //2. 加熱平底鍋,然后煎兩個雞蛋。 //Task<string> eggs = FryEggsAsync(2); string eggs =await FryEggsAsync(2); PrintInfo("雞蛋煎好了"); //3. 煎三片培根。 string bacon =await FryBaconAsync(3); PrintInfo("培根煎好了"); //4. 烤兩片面包。 string toast =await ToastBreadAsync(2); //5. 在烤面包上加黃油和果醬。 ApplyButter(toast); ApplyJam(toast); PrintInfo("面包烤好了"); //6. 倒一杯橙汁。 string oj = PourOJ(); PrintInfo("橙汁倒好了"); PrintInfo("早餐準(zhǔn)備完畢!"); watch.Stop(); TimeSpan time = watch.Elapsed; PrintInfo(string.Format("總運行時間為:{0}秒", time.TotalSeconds.ToString("0.00"))); } /// <summary> /// 異步烤面包 /// </summary> /// <param name="slices"></param> /// <returns></returns> private async Task<string> ToastBreadAsync(int slices) { for (int slice = 0; slice < slices; slice++) { PrintInfo("往烤箱里面放面包"); } PrintInfo("開始烤..."); await Task.Delay(3000); PrintInfo("從烤箱取出面包"); return "烤面包"; } /// <summary> /// 異步煎培根 /// </summary> /// <param name="slices"></param> /// <returns></returns> private async Task<string> FryBaconAsync(int slices) { PrintInfo($"放 {slices} 片培根在平底鍋"); PrintInfo("煎第一片培根..."); await Task.Delay(3000); for (int slice = 0; slice < slices; slice++) { PrintInfo("翻轉(zhuǎn)培根"); } PrintInfo("煎第二片培根..."); await Task.Delay(3000); PrintInfo("把培根放盤子里"); return "煎培根"; } /// <summary> /// 異步煎雞蛋 /// </summary> /// <param name="howMany"></param> /// <returns></returns> private async Task<string> FryEggsAsync(int howMany) { PrintInfo("加熱平底鍋..."); await Task.Delay(3000); PrintInfo($"磕開 {howMany} 個雞蛋"); PrintInfo("煎雞蛋 ..."); await Task.Delay(3000); PrintInfo("雞蛋放盤子里"); return "煎雞蛋"; }
注意:通過測試發(fā)現(xiàn),異步方式和同步方式的執(zhí)行時間一致,所以采用異步方式并不會縮短時間,但是程序已不再阻塞,可以同時響應(yīng)用戶的其他請求。
通過上述異步方式,雖然優(yōu)化了程序,不再阻塞,但是時間并沒有縮短,那么要如何優(yōu)化程序來縮短時間,以便早早的吃上可口的早餐呢?答案就是在開始一個任務(wù)后,在等待任務(wù)完成時,可以繼續(xù)進行準(zhǔn)備其他的任務(wù)。 你也幾乎將在同一時間完成所有工作。 你將吃到一頓熱氣騰騰的早餐。通過合并任務(wù)和調(diào)整任務(wù)的順序,將大大節(jié)約任務(wù)的完成時間,如下所示:
/// <summary> /// 優(yōu)化異步做早餐 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnBreakfast2_Click(object sender, EventArgs e) { this.txtInfo.Clear(); Stopwatch watch = Stopwatch.StartNew(); watch.Start(); //1. 倒一杯咖啡。 string cup = PourCoffee(); PrintInfo("咖啡沖好了"); //2. 加熱平底鍋,然后煎兩個雞蛋。 Task<string> eggsTask = FryEggsAsync(2); //3. 煎三片培根。 Task<string> baconTask = FryBaconAsync(3); //4.5合起來 烤面包,抹果醬,黃油 Task<string> toastTask = MakeToastWithButterAndJamAsync(2); string eggs = await eggsTask; PrintInfo("雞蛋煎好了"); string bacon = await baconTask; PrintInfo("培根煎好了"); string toast = await toastTask; PrintInfo("面包烤好了"); //6. 倒一杯橙汁。 string oj = PourOJ(); PrintInfo("橙汁倒好了"); PrintInfo("早餐準(zhǔn)備完畢!"); watch.Stop(); TimeSpan time = watch.Elapsed; PrintInfo(string.Format("總運行時間為:{0}秒", time.TotalSeconds.ToString("0.00"))); } /// <summary> /// 組合任務(wù) /// </summary> /// <param name="number"></param> /// <returns></returns> private async Task<string> MakeToastWithButterAndJamAsync(int number) { var toast = await ToastBreadAsync(number); ApplyButter(toast); ApplyJam(toast); return toast; }
在本例中,合并了【烤面包+抹果醬+抹黃油】為一個任務(wù),這樣是烤面包的同時,可以煎雞蛋,煎培根,三項耗時任務(wù)同時執(zhí)行。在三個任務(wù)都完成是,早餐也就做好了,示例如下所示:
通過以上優(yōu)化示例發(fā)現(xiàn),通過合并任務(wù)和調(diào)整順序,做一份早餐,需要6.06秒。
優(yōu)化后的異步做早餐,由于一些任務(wù)并發(fā)運行,因此節(jié)約了時間。示意圖如下所示:
上述示例假定所有的任務(wù)都可以正常完成,那么如果某一個任務(wù)執(zhí)行過程中發(fā)生了異常,要如何捕獲呢?答案是:當(dāng)任務(wù)無法成功完成時,它們將引發(fā)異常。 當(dāng)啟動的任務(wù)為 awaited 時,客戶端代碼可捕獲這些異常。
例如當(dāng)烤面包的時候,烤箱突然著火了,如何處理異常呢?代碼如下所示:
private async void btnBreakfastAsync3_Click(object sender, EventArgs e) { try { this.txtInfo.Clear(); Stopwatch watch = Stopwatch.StartNew(); watch.Start(); //1. 倒一杯咖啡。 string cup = PourCoffee(); PrintInfo("咖啡沖好了"); //2. 加熱平底鍋,然后煎兩個雞蛋。 Task<string> eggsTask = FryEggsAsync(2); //3. 煎三片培根。 Task<string> baconTask = FryBaconAsync(3); //4.5合起來 烤面包,抹果醬,黃油 Task<string> toastTask = MakeToastWithButterAndJamAsyncEx(2); string eggs = await eggsTask; PrintInfo("雞蛋煎好了"); string bacon = await baconTask; PrintInfo("培根煎好了"); string toast = await toastTask; PrintInfo("面包烤好了"); //6. 倒一杯橙汁。 string oj = PourOJ(); PrintInfo("橙汁倒好了"); PrintInfo("早餐準(zhǔn)備完畢!"); watch.Stop(); TimeSpan time = watch.Elapsed; PrintInfo(string.Format("總運行時間為:{0}秒", time.TotalSeconds.ToString("0.00"))); } catch (AggregateException ex) { PrintInfo("線程內(nèi)部異常"); PrintInfo(ex.StackTrace); } catch (Exception ex) { PrintInfo("其他異常"); PrintInfo(ex.Message); } } /// <summary> /// 組合任務(wù) /// </summary> /// <param name="number"></param> /// <returns></returns> private async Task<string> MakeToastWithButterAndJamAsyncEx(int number) { var toast = await ToastBreadAsyncEx(number); ApplyButter(toast); ApplyJam(toast); return toast; } /// <summary> /// 異步烤面包異常 /// </summary> /// <param name="slices"></param> /// <returns></returns> private async Task<string> ToastBreadAsyncEx(int slices) { for (int slice = 0; slice < slices; slice++) { PrintInfo("往烤箱里面放面包"); } PrintInfo("開始烤..."); await Task.Delay(2000); PrintInfo("著火了! 面包糊了!"); int a = 1, b = 0; int i = a / b;//制造一個異常 //throw new InvalidOperationException("烤箱著火了!"); await Task.Delay(1000); PrintInfo("從烤箱取出面包"); return "烤面包"; }
請注意,從烤面包機著火到發(fā)現(xiàn)異常,有相當(dāng)多的任務(wù)要完成。 當(dāng)異步運行的任務(wù)引發(fā)異常時,該任務(wù)出錯。 Task 對象包含 Task.Exception 屬性中引發(fā)的異常。 出錯的任務(wù)在等待時引發(fā)異常。
需要理解兩個重要機制:異常在出錯的任務(wù)中的存儲方式,以及在代碼等待出錯的任務(wù)時解包并重新引發(fā)異常的方式。
當(dāng)異步運行的代碼引發(fā)異常時,該異常存儲在 Task
中。 Task.Exception 屬性為 System.AggregateException,因為異步工作期間可能會引發(fā)多個異常。 引發(fā)的任何異常都將添加到 AggregateException.InnerExceptions 集合中。 如果該 Exception
屬性為 NULL,則將創(chuàng)建一個新的 AggregateException
且引發(fā)的異常是該集合中的第一項。
對于出錯的任務(wù),最常見的情況是 Exception
屬性只包含一個異常。 當(dāng)代碼 awaits
出錯的任務(wù)時,將重新引發(fā) AggregateException.InnerExceptions 集合中的第一個異常。 因此,此示例的輸出顯示 InvalidOperationException
而不是 AggregateException
。 提取第一個內(nèi)部異常使得使用異步方法與使用其對應(yīng)的同步方法盡可能相似。 當(dāng)你的場景可能生成多個異常時,可在代碼中檢查 Exception
屬性。
通過以上示例,需要等待很多任務(wù)完成,然后早餐才算做好,那么如何才能高效優(yōu)雅的等待呢?可以通過使用 Task 類的方法改進上述代碼末尾的一系列 await 語句。其中一個 API 是 WhenAll,它將返回一個其參數(shù)列表中的所有任務(wù)都已完成時才完成的 Task,如下所示:
private async void btnBreakfastAsync4_Click(object sender, EventArgs e) { this.txtInfo.Clear(); Stopwatch watch = Stopwatch.StartNew(); watch.Start(); //1. 倒一杯咖啡。 string cup = PourCoffee(); PrintInfo("咖啡沖好了"); //2. 加熱平底鍋,然后煎兩個雞蛋。 Task<string> eggsTask = FryEggsAsync(2); //3. 煎三片培根。 Task<string> baconTask = FryBaconAsync(3); //4.5合起來 烤面包,抹果醬,黃油 Task<string> toastTask = MakeToastWithButterAndJamAsync(2); //等待任務(wù)完成 await Task.WhenAll(eggsTask, baconTask, toastTask); PrintInfo("雞蛋煎好了"); PrintInfo("培根煎好了"); PrintInfo("面包烤好了"); //6. 倒一杯橙汁。 string oj = PourOJ(); PrintInfo("橙汁倒好了"); PrintInfo("早餐準(zhǔn)備完畢!"); watch.Stop(); TimeSpan time = watch.Elapsed; PrintInfo(string.Format("總運行時間為:{0}秒", time.TotalSeconds.ToString("0.00"))); }
另一種選擇是使用 WhenAny,它將返回一個當(dāng)其參數(shù)完成時才完成的 Task<Task>
。如下所示:
private async void btnBreakfastAsync5_Click(object sender, EventArgs e) { this.txtInfo.Clear(); Stopwatch watch = Stopwatch.StartNew(); watch.Start(); //1. 倒一杯咖啡。 string cup = PourCoffee(); PrintInfo("咖啡沖好了"); //2. 加熱平底鍋,然后煎兩個雞蛋。 Task<string> eggsTask = FryEggsAsync(2); //3. 煎三片培根。 Task<string> baconTask = FryBaconAsync(3); //4.5合起來 烤面包,抹果醬,黃油 Task<string> toastTask = MakeToastWithButterAndJamAsync(2); //等待任務(wù)完成 var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask }; while (breakfastTasks.Count > 0) { Task finishedTask = await Task.WhenAny(breakfastTasks); if (finishedTask == eggsTask) { PrintInfo("雞蛋煎好了"); } else if (finishedTask == baconTask) { PrintInfo("培根煎好了"); } else if (finishedTask == toastTask) { PrintInfo("面包烤好了"); } breakfastTasks.Remove(finishedTask); } //6. 倒一杯橙汁。 string oj = PourOJ(); PrintInfo("橙汁倒好了"); PrintInfo("早餐準(zhǔn)備完畢!"); watch.Stop(); TimeSpan time = watch.Elapsed; PrintInfo(string.Format("總運行時間為:{0}秒", time.TotalSeconds.ToString("0.00"))); }
“C#通過同步和異步如何實現(xiàn)優(yōu)化做早餐的時間”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。