关于async和await的探讨

缘起

最近在看《深入解析C#(第4版)》这本书,看到了第五章,这一章节是关于异步。之前对异步这个概念只能算是一知半解,了解了它的概念和用法,但是对它的实际场景和为了解决什么问题而诞生的是不太清楚的。于是乎,就和小伙伴之间有了一场讨论。

概念

一般来说对方法的调用都是同步执行的。例如在线程执行体内,即线程的调用函数中,方法的调用就是同步执行的。如果方法需要很长的时间来完成,比方说从Internet加载数据的方法,调用者线程将被阻塞直到方法调用完成。这时候为了避免调用者线程被阻塞,这时候就需要用到异步编程了。异步编程可以解决线程因为等待独占式任务而导致的阻塞问题。

探索

探索过程中,参考了《微软官方文档》,《I/O Threads Explained》。

例子说明

官方以一个做早餐的例子来解释了什么叫同步,并行和异步。

假设做一个早餐需要完成7个步骤:

  1. 倒一杯咖啡。

  2. 加热平底锅,然后煎两个鸡蛋。

  3. 煎三片培根。

  4. 烤两片面包。

  5. 在烤面包上加黄油和果酱。

  6. 倒一杯橙汁。

同步执行

同步执行,是指只有完成上一个任务,才会开始下一个任务;同时将阻塞当前线程执行其他操作,直至任务全部完成

代码例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 鉴于我用的是vs2022,可能控制台程序的代码在旧版本的vs上无法直接运行,需要补充对应的main函数
MakeBreakfast();static void MakeBreakfast()
{var cup = PourCoffee();Console.WriteLine("coffee is ready");var eggs = FryEggs(2);Console.WriteLine("eggs are ready");var bacon = FryBacon(3);Console.WriteLine("bacon is ready");var toast = ToastBread(2);ApplyButter(toast);ApplyJam(toast);Console.WriteLine("toast is ready");var oj = PourOJ();Console.WriteLine("oj is ready");Console.WriteLine("Breakfast is ready!");
}static Juice PourOJ()
{Console.WriteLine("Pouring orange juice");return new Juice();
}static void ApplyJam(Toast toast) => Console.WriteLine("Putting jam on the toast");static void ApplyButter(Toast toast) =>Console.WriteLine("Putting butter on the toast");static Toast ToastBread(int slices)
{for (int slice = 0; slice < slices; slice++){Console.WriteLine("Putting a slice of bread in the toaster");}Console.WriteLine("Start toasting...");Task.Delay(3000).Wait();Console.WriteLine("Remove toast from toaster");return new Toast();
}static Bacon FryBacon(int slices)
{Console.WriteLine($"putting {slices} slices of bacon in the pan");Console.WriteLine("cooking first side of bacon...");Task.Delay(3000).Wait();for (int slice = 0; slice < slices; slice++){Console.WriteLine("flipping a slice of bacon");}Console.WriteLine("cooking the second side of bacon...");Task.Delay(3000).Wait();Console.WriteLine("Put bacon on plate");return new Bacon();
}static Egg FryEggs(int howMany)
{Console.WriteLine("Warming the egg pan...");Task.Delay(3000).Wait();Console.WriteLine($"cracking {howMany} eggs");Console.WriteLine("cooking the eggs ...");Task.Delay(3000).Wait();Console.WriteLine("Put eggs on plate");return new Egg();
}static Coffee PourCoffee()
{Console.WriteLine("Pouring coffee");return new Coffee();
}public class Juice { }public class Bacon { }public class Egg { }public class Coffee { }public class Toast { }

949d65a1323ad42bb042886a144780f3.png

同步执行的总耗时是每个任务耗时的总和。此外,因为是同步执行的原因,在开始制作一份早餐的时候,如果此时又有一份制作早餐的请求过来,是不会开始制作的。如果是客户端程序,使用同步执行耗时时间长的操作,会导致UI线程被阻塞,导致UI线程无法响应用户操作,直至操作完成后,UI线程才相应用户的操作。

异步执行

异步执行,是指在遇到await的时候,才需要等待异步操作完成,然后往下执行;但是不会阻塞当前线程执行其他操作。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
await MakeBreakfastAsync();static async Task MakeBreakfastAsync()
{var cup = PourCoffee();Console.WriteLine("coffee is ready");var eggs = await FryEggsAsync(2);Console.WriteLine("eggs are ready");var bacon = await FryBaconAsync(3);Console.WriteLine("bacon is ready");var toast = await ToastBreadAsync(2);ApplyButter(toast);ApplyJam(toast);Console.WriteLine("toast is ready");var oj = PourOJ();Console.WriteLine("oj is ready");Console.WriteLine("Breakfast is ready!");
}static async Task<Toast> ToastBreadAsync(int slices)
{for (int slice = 0; slice < slices; slice++){Console.WriteLine("Putting a slice of bread in the toaster");}Console.WriteLine("Start toasting...");Task.Delay(3000).Wait();Console.WriteLine("Remove toast from toaster");return await Task.FromResult(new Toast());
}static Task<Bacon> FryBaconAsync(int slices)
{Console.WriteLine($"putting {slices} slices of bacon in the pan");Console.WriteLine("cooking first side of bacon...");Task.Delay(3000).Wait();for (int slice = 0; slice < slices; slice++){Console.WriteLine("flipping a slice of bacon");}Console.WriteLine("cooking the second side of bacon...");Task.Delay(3000).Wait();Console.WriteLine("Put bacon on plate");return Task.FromResult(new Bacon());
}static Task<Egg> FryEggsAsync(int howMany)
{Console.WriteLine("Warming the egg pan...");Task.Delay(3000).Wait();Console.WriteLine($"cracking {howMany} eggs");Console.WriteLine("cooking the eggs ...");Task.Delay(3000).Wait();Console.WriteLine("Put eggs on plate");return Task.FromResult(new Egg());
}

6d469afb67c400ca5a6aa9ba6a3ff39b.png

上面代码只是为了避免堵塞当前的线程,并没有真正用上异步执行的某些关键功能,所以在耗时上是相差不远的;但是这时候如果在接受了一份制作早餐的请求,还未完成的时候,又有一份制作早餐的请求过来,是可能会开始制作另一份早餐的。

改善后的异步执行

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
await MakeBreakfastBetterAsync();static async Task MakeBreakfastBetterAsync()
{Coffee cup = PourCoffee();Console.WriteLine("Coffee is ready");Task<Egg> eggsTask = FryEggsAsync(2);Task<Bacon> baconTask = FryBaconAsync(3);Task<Toast> toastTask = ToastBreadAsync(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 eggsTask;Console.WriteLine("Eggs are ready");Bacon bacon = await baconTask;Console.WriteLine("Bacon is ready");Console.WriteLine("Breakfast is ready!");
}

异步方法的逻辑没有改变,只是调整了一下代码的执行顺序,一开始就调用了三个异步方法,只是在await语句后置了,而不是上面那段代码一样,执行了就在那里等待任务完成,而是会去进行其他的后续操作,直至后续操作需要用到前面任务执行结果的时候,才去获取对应的执行结果,如果没有执行完成就等待执行完成才继续后续的操作。

异步执行并不总是需要另一个线程来执行新任务。并行编程是异步执行的一个子集。

并行编程

并行编程,调用多个线程,同时去执行任务

例如:需要制作五份早餐,同步和异步的方法都是需要循环调用相应的MakeBreakfast方法和MakeBreakfastBetterAsync方法五次才能制作完成。而并行编程,也就是多线程,可以一次性创建五个线程,分别制作一份早餐,从而大大缩短了所需要的时间。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
DateTime beforeDT = DateTime.Now;
for (int i = 0; i < 5; i++)
{MakeBreakfast();
}
DateTime afterDT = DateTime.Now;
TimeSpan ts = afterDT.Subtract(beforeDT);
Console.WriteLine($"同步执行程序耗时: {ts.TotalMilliseconds}ms");beforeDT = DateTime.Now;
for (int i = 0; i < 5; i++)
{await MakeBreakfastBetterAsync();
}
afterDT = DateTime.Now;
ts = afterDT.Subtract(beforeDT);
Console.WriteLine($"异步执行程序耗时: {ts.TotalMilliseconds}ms");beforeDT = DateTime.Now;
await MakeBreakfastBetterMultiTask();
afterDT = DateTime.Now;
ts = afterDT.Subtract(beforeDT);
Console.WriteLine($"并行编程程序耗时: {ts.TotalMilliseconds}ms");static async Task MakeBreakfastBetterMultiTask()
{Task[] tasks = new Task[5];for (int i = 0; i < 5; i++){tasks[i] = new Task((parameter) => MakeBreakfastBetterAsync().Wait(), "aaa");tasks[i].Start();}Task.WaitAll(tasks);
}

运行耗时结果如下

d6eae350160f8aeb7751e7116e1a4354.png

37f8f64b737948121dac4fbaaa0b0406.png

7c0b61696aba3541b6324b129f7e71b3.png

相比之下,显然能看出来之间的运行耗时差别还是有点大的。

一个通俗的例子

程序就像一个餐馆,线程就像餐馆里面已有的厨师,CPU就是调度厨师的厨师长,假设餐馆开业了,厨师长只带了5个厨师,餐馆接到的订单有8份,同步执行就是5个厨师分别处理5个订单后,这期间,他们会专心的去完成订单的菜,而无视其他的事情,直到完成订单,厨师长才会分配新的订单给他们;异步执行则是5个厨师在处理5个订单的期间,如果厨师长发现他们有人处于空闲状态,就会安排他们去执行剩下3个订单,如果收到等待中的订单可以继续操作时,厨师长会抽调厨师继续完成订单,从而增加了餐馆处理订单的能力。而并行编程则是餐馆开业的时候,告诉了厨师长,需要8个厨师;厨师长就带来了相应数量的厨师来处理订单。

这就是这两天,我对同步,异步和并行之间的感悟。如有不对,敬请指正!

推荐阅读:

《Kubernetes全栈架构师(Kubeadm高可用安装k8s集群)--学习笔记》

《.NET 云原生架构师训练营(模块一 架构师与云原生)--学习笔记》

《.NET Core开发实战(第1课:课程介绍)--学习笔记》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294255.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

令人惋惜的天才新秀:16岁上剑桥大学,27岁就出名,数学事业一路畅通无阻,但自从结婚后,人生从此翻天覆地······

全世界只有3.14 % 的人关注了爆炸吧知识据说&#xff0c;在网上流传着这么一则笑话&#xff1a;“费马&#xff0c;泰勒&#xff0c;拉格朗日&#xff0c;洛必达&#xff0c;史称&#xff1a;“马勒格必”。嘘&#xff01;这可不是在口吐芬芳&#xff0c;这可是让无数高数学渣头…

jQuery的.live()和.die()

2019独角兽企业重金招聘Python工程师标准>>> 很多开发者都知道jQuery的.live()方法&#xff0c;他们大部分知道这个函数做什么&#xff0c;但是并不知道是怎么实现的&#xff0c;所以用的并不那么舒适。而且他们却从未听过还有解除绑定的.live()事件的.die()方法。即…

简洁强大的JavaWeb框架Blade

English Blade是什么? blade 是一个轻量级的MVC框架. 它拥有简洁的代码&#xff0c;优雅的设计。 如果你喜欢,欢迎 Star and Fork, 谢谢! 特性 [x] 轻量级。代码简洁,结构清晰,更容易开发[x] 模块化(你可以选择使用哪些组件)[x] 插件扩展机制[x] Restful风格的路由接口[x] 多种…

SQL SERVER 数据压缩

从SQL SERVER 2008开始&#xff0c;SQL SERVER 提供了对数据进行压缩的功能&#xff0c;启用数据压缩无须修改应用程序。 数据压缩可有效减少数据的占用空间&#xff0c;读取和写入相同数据花费的IO也响应减少&#xff0c;从而可以有效缓解IO压力&#xff0c;但由于数据在读取和…

C语言基本数据类型short、int、long、char、float、double

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到教程 1.概述 C 语言包含的数据类型如下图所示 2.各种数据类型介绍 2.1整型 整形包括短整型、整形和长整形。 2.1.1短整形 short a=1; 2.1.2整形 一般占4个字节(32位),…

oracle存储回车换行,oracle中去掉回车换行空格的方法详解

去除换行update zhzl_address t set t.add_administration_numreplace(t.add_administration_num,chr(10),);去掉回车update zhzl_address t set t.add_administration_numreplace(t.add_administration_num,chr(13),);去掉空格update zhzl_address t set t.add_administration…

是的,我打败了一个奥特曼。

全世界只有3.14 % 的人关注了爆炸吧知识春节将至&#xff0c;超模君给各位带来了春节好礼——《数学之旅闪耀人类的54位数学家》文化礼盒&#xff0c;集结阿基米德、欧几里得、芝诺、高斯、欧拉等54位数学家&#xff0c;跨越2600年的数学之旅&#xff0c;为各位新一年的学习之旅…

博弈论之软件测试的价值

百度搜索&#xff1a;小强测试品牌挨踢脱口秀&#xff0c;将技术娱乐化&#xff0c;碎片系统化&#xff0c;尽在荔枝FM&#xff08;http://www.lizhi.fm/200893&#xff09;1、引子这段时间比较火热的话题就是公司是否需要软件测试工程师&#xff0c;N多大牛和公司领导都纷纷站…

C语言常用头文件总结

序号库类别 头文件 1 字符处理 ctype.h 2 地区化 local.h 3 数学函数 math.h 4 信号处理 signal.h 5 输入输出 stdio.h 6 实用工具程序 stdlib.h 7 字符串处理 string.h 一、头文件 ctype.h 字符测试函数 是否字母和数字 isalnum 是否字母 isalpha 是…

89C51单片机定时器控制的流水灯

/***************************************************Copyright: 2014-02-11.version1.0File name: timer.cDescription: 定时中断控制的流水灯Author: lxlVersion: version1.0Date: 2014.02.11History: 无****************************************************/#include &l…

Oracle 加密配置,Oracle sqlnet设置网络传输加密

1、查看加密组件[oracleyuntestdb ~]$ adaptersInstalled Oracle Net transport protocols are:IPCBEQTCP/IPSSLRAWSDP/IBInstalled Oracle Net naming methods are:Local Naming (tnsnames.ora)Oracle Directory NamingOracle Host NamingOracle Names Server NamingInstalled…

你有多少个前男友?

1 给我整不会了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 这是送分题还是送命题&#xff1f;▼3 有梦想谁都了不起&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 &#xff1f;&#xff1f;&#xff08;via.偷听bot&#xff0c;侵删&#xff09;…

apache服务器搭建过程中需要注意到的一些问题

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/chengyi_L/article/details/51423390 1.日志切分 windows: 利用自带工具 rotatelogs切分access.log和error.log ErrorLog "|bin/rotatelogs.exe logs/apache_error-%Y-%…

C实现记录个人资料

代码: #include <stdio.h> #include <ctype.h> #include <stdlib.h> #include <string.h>struct Family *get_person(void); /* Prototype for input function */ char related(struct Family *pmember1, struct Family *pmember2); char set_ance…

Hibernate type 与java 和 数据库类型对应

Hibernate type 属性 内置的 basic mapping types 可以大致地分类为&#xff1a; integer, long, short, float, double, character, byte, boolean, yes_no, true_false这些类型都对应 Java 的原始类型或者其封装类&#xff0c;来符合&#xff08;特定厂商的&#xff09;SQL 字…

Log4j.properties 配置详解

一、Log4j简介 Log4j有三个主要的组件&#xff1a;Loggers(记录器)&#xff0c;Appenders (输出源)和Layouts(布局)。这里可简单理解为日志类别&#xff0c;日志要输出的地方和日志以何种形式输出。综合使用这三个组件可以轻松地记录信息的类型和级别&#xff0c;并可以在运行时…

oracle查询表占用空,Oracle 表的行数、表占用空间大小,列的非空行数、列占用空间大小 查询...

--表名,表占用空间大小(MB),行数select table_name, round(num_rows * avg_row_len /1024/1024, 8) as total_len, num_rowsfrom user_tableswhere table_name ‘表名‘order by table_name;--表名,列名,列占用空间大小(MB),非空行数select c.table_name, c.column_name, roun…

双链表的实现

#include <stdio.h> #include <string.h> #include <stdlib.h> #define N 10 typedef struct node {char name[20];struct node *llink,*rlink; }stud;/*双链表的结构定义*//*双链表的创建*/ stud * creat(int n) {stud *p,*h,*s;int i;if((h=(stud *)malloc…

里程碑!中文版.NET官网发布,.NET开发起飞!

重磅官宣&#xff0c;微软.NET官网正式发布简体中⽂版&#xff0c;而且是官方支持语言第一梯队&#xff0c;由此可见微软对中国.NET市场的重视。这些年&#xff0c;微软一直在加大.NET在中国的支持和推广&#xff0c;努力让更多传统行业、互联网公司、创业团队的中国开发者选择…

android中Camera setDisplayOrientation使用

在 写相机相关应用的时候遇到捕获的画面方向和手机的方向不一致的问题&#xff0c;比如手机是竖着拿的&#xff0c;但是画面是横的&#xff0c;这是由于摄像头默认捕获的画面byte[]是根 据横向来的&#xff0c;而你的应用是竖向的&#xff0c;解决办法是调用setDisplayOrientat…