• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

C#从做早餐看同步异步

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

概述

一天之计在于晨,每天的早餐也是必不可少,但是很多人为了节约时间,都是简单的吃点凑合一下或干脆不吃早餐,这对于个人身体和工作效率来说,无疑是不合理的,那么要如何做一顿早餐呢?如何能节约做早餐的时间呢?本文以一个简单的小例子,简述如何做一顿早餐及如何优化做早餐的时间。仅供学习分享使用,如有不足之处,还请指正。

正常情况下,做早餐可以分为以下几个步骤:

  1. 倒一杯咖啡。
  2. 加热平底锅,然后煎两个鸡蛋。
  3. 煎三片培根。
  4. 烤两片面包。
  5. 在烤面包上加黄油和果酱。
  6. 倒一杯橙汁。

同步方式做早餐

根据以上步骤进行编程,做一份早餐需要编写程序如下:

  1         /// <summary>
  2         /// 同步做早餐
  3         /// </summary>
  4         /// <param name="sender"></param>
  5         /// <param name="e"></param>
  6         private void btnBreakfast_Click(object sender, EventArgs e)
  7         {
  8             this.txtInfo.Clear();
  9             Stopwatch watch = Stopwatch.StartNew();
 10             watch.Start();
 11             //1. 倒一杯咖啡。
 12             string cup = PourCoffee();
 13             PrintInfo("咖啡冲好了");
 14             //2. 加热平底锅,然后煎两个鸡蛋。 
 15             string eggs = FryEggs(2);
 16             PrintInfo("鸡蛋煎好了");
 17             //3. 煎三片培根。 
 18             string bacon = FryBacon(3);
 19             PrintInfo("培根煎好了");
 20             //4. 烤两片面包。 
 21             string toast = ToastBread(2);
 22             //5. 在烤面包上加黄油和果酱。
 23             ApplyButter(toast);
 24             ApplyJam(toast);
 25             PrintInfo("面包烤好了");
 26             //6. 倒一杯橙汁。
 27             string oj = PourOJ();
 28             PrintInfo("橙汁倒好了");
 29             PrintInfo("早餐准备完毕!");
 30             watch.Stop();
 31             TimeSpan time = watch.Elapsed;
 32             PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
 33         }
 34 
 35         /// <summary>
 36         /// 倒一杯咖啡
 37         /// </summary>
 38         /// <returns></returns>
 39         private string PourCoffee()
 40         {
 41             PrintInfo("正在冲咖啡...");
 42             return "咖啡";
 43         }
 44 
 45         /// <summary>
 46         /// 抹果酱
 47         /// </summary>
 48         /// <param name="toast"></param>
 49         private void ApplyJam(string toast) =>
 50             PrintInfo("往面包抹果酱");
 51 
 52         /// <summary>
 53         /// 抹黄油
 54         /// </summary>
 55         /// <param name="toast"></param>
 56         private void ApplyButter(string toast) =>
 57             PrintInfo("往面包抹黄油");
 58 
 59         /// <summary>
 60         /// 烤面包
 61         /// </summary>
 62         /// <param name="slices"></param>
 63         /// <returns></returns>
 64         private string ToastBread(int slices)
 65         {
 66             for (int slice = 0; slice < slices; slice++)
 67             {
 68                 PrintInfo("往烤箱里面放面包");
 69             }
 70             PrintInfo("开始烤...");
 71             Task.Delay(3000).Wait();
 72             PrintInfo("从烤箱取出面包");
 73 
 74             return "烤面包";
 75         }
 76 
 77         /// <summary>
 78         /// 煎培根
 79         /// </summary>
 80         /// <param name="slices"></param>
 81         /// <returns></returns>
 82         private string FryBacon(int slices)
 83         {
 84             PrintInfo($"放 {slices} 片培根在平底锅");
 85             PrintInfo("煎第一片培根...");
 86             Task.Delay(3000).Wait();
 87             for (int slice = 0; slice < slices; slice++)
 88             {
 89                 PrintInfo("翻转培根");
 90             }
 91             PrintInfo("煎第二片培根...");
 92             Task.Delay(3000).Wait();
 93             PrintInfo("把培根放盘子里");
 94 
 95             return "煎培根";
 96         }
 97 
 98         /// <summary>
 99         /// 煎鸡蛋
100         /// </summary>
101         /// <param name="howMany"></param>
102         /// <returns></returns>
103         private string FryEggs(int howMany)
104         {
105             PrintInfo("加热平底锅...");
106             Task.Delay(3000).Wait();
107             PrintInfo($"磕开 {howMany} 个鸡蛋");
108             PrintInfo("煎鸡蛋 ...");
109             Task.Delay(3000).Wait();
110             PrintInfo("鸡蛋放盘子里");
111 
112             return "煎鸡蛋";
113         }
114 
115         /// <summary>
116         /// 倒橙汁
117         /// </summary>
118         /// <returns></returns>
119         private string PourOJ()
120         {
121             PrintInfo("倒一杯橙汁");
122             return "橙汁";
123         }

同步做早餐示例

通过运行示例,发现采用同步方式进行编程,做一份早餐,共计15秒钟,且在此15秒钟时间内,程序处于【卡住】状态,无法进行其他操作。如下所示:

同步做早餐示意图

同步方式做早餐,就是一个做完,再进行下一个,顺序执行,如下所示:

同步方式为何会【卡住】?

因为在程序进程中,会有一个主线程,用于响应用户的操作,同步方式下,做早餐的和前端页面同在主线程中,所以当开始做早餐时,就不能响应其他的操作了。这就是【两耳不闻窗外事,一心只读圣贤书】的境界。但如果让用户长时间处于等待状态,会让用户体验很不友好。比如,刘玄德三顾茅庐,大雪纷飞之下,诸葛亮在草庐中午睡,刘关张在大雪中静等。试问有几人会有玄德的耐心,何况程序也不是诸葛亮,用户也没有玄德的耐心!

异步方式做早餐

上述代码演示了不正确的实践:构造同步代码来执行异步操作。 顾名思义,此代码将阻止执行这段代码的线程执行任何其他操作。 在任何任务进行过程中,此代码也不会被中断。 就如同你将面包放进烤面包机后盯着此烤面包机一样。 你会无视任何跟你说话的人,直到面包弹出。如何做才能避免线程阻塞呢?答案就是异步。 await 关键字提供了一种非阻塞方式来启动任务,然后在此任务完成时继续执行。

首先更新代码,对于耗时的程序,采用异步方式做早餐,如下所示:

 1         private async void btnBreakfastAsync_Click(object sender, EventArgs e)
 2         {
 3             this.txtInfo.Clear();
 4             Stopwatch watch = Stopwatch.StartNew();
 5             watch.Start();
 6             //1. 倒一杯咖啡。
 7             string cup = PourCoffee();
 8             PrintInfo("咖啡冲好了");
 9             //2. 加热平底锅,然后煎两个鸡蛋。 
10             //Task<string> eggs = FryEggsAsync(2);
11             string eggs =await FryEggsAsync(2);
12             PrintInfo("鸡蛋煎好了");
13             //3. 煎三片培根。 
14             string bacon =await FryBaconAsync(3);
15             PrintInfo("培根煎好了");
16             //4. 烤两片面包。 
17             string toast =await ToastBreadAsync(2);
18             //5. 在烤面包上加黄油和果酱。
19             ApplyButter(toast);
20             ApplyJam(toast);
21             PrintInfo("面包烤好了");
22             //6. 倒一杯橙汁。
23             string oj = PourOJ();
24             PrintInfo("橙汁倒好了");
25             PrintInfo("早餐准备完毕!");
26             watch.Stop();
27             TimeSpan time = watch.Elapsed;
28             PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
29         }
30 
31         /// <summary>
32         /// 异步烤面包
33         /// </summary>
34         /// <param name="slices"></param>
35         /// <returns></returns>
36         private async Task<string> ToastBreadAsync(int slices)
37         {
38             for (int slice = 0; slice < slices; slice++)
39             {
40                 PrintInfo("往烤箱里面放面包");
41             }
42             PrintInfo("开始烤...");
43             await Task.Delay(3000);
44             PrintInfo("从烤箱取出面包");
45 
46             return "烤面包";
47         }
48 
49         /// <summary>
50         /// 异步煎培根
51         /// </summary>
52         /// <param name="slices"></param>
53         /// <returns></returns>
54         private async Task<string> FryBaconAsync(int slices)
55         {
56             PrintInfo($"放 {slices} 片培根在平底锅");
57             PrintInfo("煎第一片培根...");
58             await Task.Delay(3000);
59             for (int slice = 0; slice < slices; slice++)
60             {
61                 PrintInfo("翻转培根");
62             }
63             PrintInfo("煎第二片培根...");
64             await Task.Delay(3000);
65             PrintInfo("把培根放盘子里");
66 
67             return "煎培根";
68         }
69 
70         /// <summary>
71         /// 异步煎鸡蛋
72         /// </summary>
73         /// <param name="howMany"></param>
74         /// <returns></returns>
75         private async Task<string> FryEggsAsync(int howMany)
76         {
77             PrintInfo("加热平底锅...");
78             await Task.Delay(3000);
79             PrintInfo($"磕开 {howMany} 个鸡蛋");
80             PrintInfo("煎鸡蛋 ...");
81             await Task.Delay(3000);
82             PrintInfo("鸡蛋放盘子里");
83 
84             return "煎鸡蛋";
85         }

 注意:通过测试发现,异步方式和同步方式的执行时间一致,所以采用异步方式并不会缩短时间,但是程序已不再阻塞,可以同时响应用户的其他请求

优化异步做早餐

通过上述异步方式,虽然优化了程序,不再阻塞,但是时间并没有缩短,那么要如何优化程序来缩短时间,以便早早的吃上可口的早餐呢?答案就是在开始一个任务后,在等待任务完成时,可以继续进行准备其他的任务。 你也几乎将在同一时间完成所有工作。 你将吃到一顿热气腾腾的早餐。通过合并任务和调整任务的顺序,将大大节约任务的完成时间,如下所示:

 1         /// <summary>
 2         /// 优化异步做早餐
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private async void btnBreakfast2_Click(object sender, EventArgs e)
 7         {
 8             this.txtInfo.Clear();
 9             Stopwatch watch = Stopwatch.StartNew();
10             watch.Start();
11             //1. 倒一杯咖啡。
12             string cup = PourCoffee();
13             PrintInfo("咖啡冲好了");
14             //2. 加热平底锅,然后煎两个鸡蛋。 
15             Task<string> eggsTask = FryEggsAsync(2);
16             //3. 煎三片培根。 
17             Task<string> baconTask = FryBaconAsync(3);
18             //4.5合起来 烤面包,抹果酱,黄油
19             Task<string> toastTask = MakeToastWithButterAndJamAsync(2);
20 
21             string eggs = await eggsTask;
22             PrintInfo("鸡蛋煎好了");
23 
24             string bacon = await baconTask;
25             PrintInfo("培根煎好了");
26 
27             string toast = await toastTask;
28             PrintInfo("面包烤好了");
29             //6. 倒一杯橙汁。
30             string oj = PourOJ();
31             PrintInfo("橙汁倒好了");
32             PrintInfo("早餐准备完毕!");
33             watch.Stop();
34             TimeSpan time = watch.Elapsed;
35             PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
36         }
37 
38         /// <summary>
39         /// 组合任务
40         /// </summary>
41         /// <param name="number"></param>
42         /// <returns></returns>
43         private async Task<string> MakeToastWithButterAndJamAsync(int number)
44         {
45             var toast = await ToastBreadAsync(number);
46             ApplyButter(toast);
47             ApplyJam(toast);
48             return toast;
49         }

在本例中,合并了【烤面包+抹果酱+抹黄油】为一个任务,这样是烤面包的同时,可以煎鸡蛋,煎培根,三项耗时任务同时执行。在三个任务都完成是,早餐也就做好了,示例如下所示:

通过以上优化示例发现,通过合并任务和调整顺序,做一份早餐,需要6.06秒。

优化异步早餐示意图

优化后的异步做早餐,由于一些任务并发运行,因此节约了时间。示意图如下所示:

异步异常

上述示例假定所有的任务都可以正常完成,那么如果某一个任务执行过程中发生了异常,要如何捕获呢?答案是:当任务无法成功完成时,它们将引发异常。 当启动的任务为 awaited 时,客户端代码可捕获这些异常。

例如当烤面包的时候,烤箱突然着火了,如何处理异常呢?代码如下所示:

 1         private async void btnBreakfastAsync3_Click(object sender, EventArgs e)
 2         {
 3             try
 4             {
 5                 this.txtInfo.Clear();
 6                 Stopwatch watch = Stopwatch.StartNew();
 7                 watch.Start();
 8                 //1. 倒一杯咖啡。
 9                 string cup = PourCoffee();
10                 PrintInfo("咖啡冲好了");
11                 //2. 加热平底锅,然后煎两个鸡蛋。 
12                 Task<string> eggsTask = FryEggsAsync(2);
13                 //3. 煎三片培根。 
14                 Task<string> baconTask = FryBaconAsync(3);
15                 //4.5合起来 烤面包,抹果酱,黄油
16                 Task<string> toastTask = MakeToastWithButterAndJamAsyncEx(2);
17 
18                 string eggs = await eggsTask;
19                 PrintInfo("鸡蛋煎好了");
20 
21                 string bacon = await baconTask;
22                 PrintInfo("培根煎好了");
23 
24                 string toast = await toastTask;
25                 PrintInfo("面包烤好了");
26                 //6. 倒一杯橙汁。
27                 string oj = PourOJ();
28                 PrintInfo("橙汁倒好了");
29                 PrintInfo("早餐准备完毕!");
30                 watch.Stop();
31                 TimeSpan time = watch.Elapsed;
32                 PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
33             }
34             catch (AggregateException ex) {
35                 PrintInfo("线程内部异常");
36                 PrintInfo(ex.StackTrace);
37             }
38             catch (Exception ex)
39             {
40                 PrintInfo("其他异常");
41                 PrintInfo(ex.Message);
42             }
43         }
44 
45         /// <summary>
46         /// 组合任务
 
                       
                    
                    

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#检测浏览器类型DetectBrowser发布时间:2022-07-18
下一篇:
C#实现消息队列MSMQ发布时间:2022-07-18
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap