溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C#通過同步和異步如何實現(xiàn)優(yōu)化做早餐的時間

發(fā)布時間:2021-12-06 10:13:50 來源:億速云 閱讀:148 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“C#通過同步和異步如何實現(xiàn)優(yōu)化做早餐的時間”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

    概述

    一天之計在于晨,每天的早餐也是必不可少,但是很多人為了節(jié)約時間,都是簡單的吃點湊合一下或干脆不吃早餐,這對于個人身體和工作效率來說,無疑是不合理的,那么要如何做一頓早餐呢?如何能節(jié)約做早餐的時間呢?本文以一個簡單的小例子,簡述如何做一頓早餐及如何優(yōu)化做早餐的時間。僅供學(xué)習(xí)分享使用,如有不足之處,還請指正。

    正常情況下,做早餐可以分為以下幾個步驟:

    1. 倒一杯咖啡。

    2. 加熱平底鍋,然后煎兩個雞蛋。

    3. 煎三片培根。

    4. 烤兩片面包。

    5. 在烤面包上加黃油和果醬。

    6. 倒一杯橙汁。

    同步方式做早餐

    根據(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),無法進行其他操作。如下所示:

    C#通過同步和異步如何實現(xiàn)優(yōu)化做早餐的時間

    同步做早餐示意圖

    同步方式做早餐,就是一個做完,再進行下一個,順序執(zhí)行,如下所示:

    C#通過同步和異步如何實現(xiàn)優(yōu)化做早餐的時間

    同步方式為何會【卡住】?

    因為在程序進程中,會有一個主線程,用于響應(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)化了程序,不再阻塞,但是時間并沒有縮短,那么要如何優(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ù)都完成是,早餐也就做好了,示例如下所示:

    C#通過同步和異步如何實現(xiàn)優(yōu)化做早餐的時間

    通過以上優(yōu)化示例發(fā)現(xiàn),通過合并任務(wù)和調(diào)整順序,做一份早餐,需要6.06秒。

    優(yōu)化異步早餐示意圖

    優(yōu)化后的異步做早餐,由于一些任務(wù)并發(fā)運行,因此節(jié)約了時間。示意圖如下所示:

    C#通過同步和異步如何實現(xiàn)優(yōu)化做早餐的時間

    異步異常

    上述示例假定所有的任務(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 "烤面包";
            }

    異步任務(wù)異常示例

    C#通過同步和異步如何實現(xiàn)優(yōu)化做早餐的時間

    請注意,從烤面包機著火到發(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ì)量的實用文章!

    向AI問一下細節(jié)

    免責(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)容。

    AI