一文说通C#中的异步编程

天天写,不一定就明白。

又及,前两天看了一个关于同步方法中调用异步方法的文章,里面有些概念不太正确,所以整理了这个文章。

一、同步和异步。

先说同步。

同步概念大家都很熟悉。在异步概念出来之前,我们的代码都是按同步的方式写的。简单来说,就是程序严格按照代码的逻辑次序,一行一行执行。

看一段代码:

public static void Main(string[] args)
{Console.WriteLine("Syc proccess - start");Console.WriteLine("Syc proccess - enter Func1");func1();Console.WriteLine("Syc proccess - out Func1");Console.WriteLine("Syc proccess - enter Func2");func2();Console.WriteLine("Syc proccess - out Func2");Console.WriteLine("Syc proccess - enter Func3");func3();Console.WriteLine("Syc proccess - out Func3");Console.WriteLine("Syc proccess - done");
}private static void func1()
{Console.WriteLine("Func1 proccess - start");Thread.Sleep(1000);Console.WriteLine("Func1 proccess - end");
}private static void func2()
{Console.WriteLine("Func2 proccess - start");Thread.Sleep(3000);Console.WriteLine("Func2 proccess - end");
}private static void func3()
{Console.WriteLine("Func3 proccess - start");Thread.Sleep(5000);Console.WriteLine("Func3 proccess - end");
}

这是一段简单的通常意义上的代码,程序按代码的次序同步执行,看结果:

Syc proccess - start
Syc proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Syc proccess - out Func1
Syc proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Syc proccess - out Func2
Syc proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Syc proccess - out Func3
Syc proccess - done

没有任何意外。

那异步呢?

异步,来自于对同步处理的改良和优化。

应用中,经常会有对于文件或网络、数据库的IO操作。这些操作因为IO软硬件的原因,需要消耗很多时间,但通常情况下CPU计算量并不大。在同步的代码中,这个过程会被阻塞。直白的说法就是这一行代码没执行完成,程序就得等着,等完成后再执行下一行代码,而这个等待的时间中,CPU资源就被浪费了,闲着了,什么也没做。(当然,操作系统会调度CPU干别的,这儿不抬杠。)

异步编程模型和规范因此出现了,通过某种机制,让程序在等着IO的过程中,继续做点别的事,等IO的过程完成了,再回来处理IO的内容。这样CPU也没闲着,在等IO的过程中多做了点事。反映到用户端,就感觉程序更快了,用时更短了。

下面重点说一下异步编程相关的内容。

二、异步编程

C#中,异步编程,一个核心,两个关键字。

一个核心是指TaskTask<T>对象,而两个关键字,就是asyncawait

从各种渠道给出的异步编程,都是下面的方式:

async Task function()
{/* your code here */
}

然后调用的方式:

await function();

是这样的吗?嗯,图样图森破~~~

我们来看代码:

static async Task Main(string[] args)
{Console.WriteLine("Async proccess - start");Console.WriteLine("Async proccess - enter Func1");await func1();Console.WriteLine("Async proccess - out Func1");Console.WriteLine("Async proccess - enter Func2");await func2();Console.WriteLine("Async proccess - out Func2");Console.WriteLine("Async proccess - enter Func3");await func3();Console.WriteLine("Async proccess - out Func3");Console.WriteLine("Async proccess - done");Console.WriteLine("Main proccess - done");
}private static async Task func1()
{Console.WriteLine("Func1 proccess - start");Thread.Sleep(1000);Console.WriteLine("Func1 proccess - end");
}private static async Task func2()
{Console.WriteLine("Func2 proccess - start");Thread.Sleep(3000);Console.WriteLine("Func2 proccess - end");
}private static async Task func3()
{Console.WriteLine("Func3 proccess - start");Thread.Sleep(5000);Console.WriteLine("Func3 proccess - end");
}

跑一下结果:

Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Async proccess - out Func3
Async proccess - done
Main proccess - done

咦?这个好像跟同步代码的执行结果没什么区别啊?

嗯,完全正确。上面这个代码,真的是同步执行的。

这是异步编程的第一个容易错误的理解:asyncawait的配对。

三、async和await的配对

在异步编程的规范中,async修饰的方法,仅仅表示这个方法在内部有可能采用异步的方式执行,CPU在执行这个方法时,会放到一个新的线程中执行。

那这个方法,最终是否采用异步执行,不决定于是否用await方式调用这个方法,而决定于这个方法内部,是否有await方式的调用。

看代码,很容易理解:

private static async Task func1()
{Console.WriteLine("Func1 proccess - start");Thread.Sleep(1000);Console.WriteLine("Func1 proccess - end");
}

这个方法,因为方法内部没有await调用,所以这个方法永远会以同步方式执行,不管你调用这个方法时,有没有await

而下面这个代码:

private static async Task func1()
{Console.WriteLine("Func1 proccess - start");await Task.Run(() => Thread.Sleep(1000));Console.WriteLine("Func1 proccess - end");
}

因为这个方法里有await调用,所以这个方法不管你以什么方式调用,有没有await,都是异步执行的。

看代码:

static async Task Main(string[] args)
{Console.WriteLine("Async proccess - start");Console.WriteLine("Async proccess - enter Func1");func1();Console.WriteLine("Async proccess - out Func1");Console.WriteLine("Async proccess - enter Func2");func2();Console.WriteLine("Async proccess - out Func2");Console.WriteLine("Async proccess - enter Func3");func3();Console.WriteLine("Async proccess - out Func3");Console.WriteLine("Async proccess - done");Console.WriteLine("Main proccess - done");Console.ReadKey();
}private static async Task func1()
{Console.WriteLine("Func1 proccess - start");await Task.Run(() => Thread.Sleep(1000));Console.WriteLine("Func1 proccess - end");
}private static async Task func2()
{Console.WriteLine("Func2 proccess - start");await Task.Run(() => Thread.Sleep(3000));Console.WriteLine("Func2 proccess - end");
}private static async Task func3()
{Console.WriteLine("Func3 proccess - start");await Task.Run(() => Thread.Sleep(5000));Console.WriteLine("Func3 proccess - end");
}

输出结果:

Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Async proccess - out Func3
Async proccess - done
Main proccess - done
Func1 proccess - end
Func2 proccess - end
Func3 proccess - end

结果中,在长时间运行Thread.Sleep的时候,跳出去往下执行了,是异步。

又有问题来了:不是说异步调用要用await吗?

我们把await加到调用方法的前边,试一下:

static async Task Main(string[] args)
{Console.WriteLine("Async proccess - start");Console.WriteLine("Async proccess - enter Func1");await func1();Console.WriteLine("Async proccess - out Func1");Console.WriteLine("Async proccess - enter Func2");await func2();Console.WriteLine("Async proccess - out Func2");Console.WriteLine("Async proccess - enter Func3");await func3();Console.WriteLine("Async proccess - out Func3");Console.WriteLine("Async proccess - done");Console.WriteLine("Main proccess - done");Console.ReadKey();
}

跑一下结果:

Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Async proccess - out Func3
Async proccess - done
Main proccess - done

嗯?怎么又像是同步了?

对,这是第二个容易错误的理解:await是什么意思?

四、await是什么意思

提到await,就得先说说Wait

字面意思,Wait就是等待。

前边说了,异步有一个核心,是Task。而Task有一个方法,就是Wait,写法是Task.Wait()。所以,很多人把这个Waitawait混为一谈,这是错的

这个问题来自于Task。C#里,Task不是专为异步准备的,它表达的是一个线程,是工作在线程池里的一个线程。异步是线程的一种应用,多线程也是线程的一种应用。Wait,以及StatusIsCanceledIsCompletedIsFaulted等等,是给多线程准备的方法,跟异步没有半毛钱关系。当然你非要在异步中使用多线程的Wait或其它,从代码编译层面不会出错,但程序会。

尤其,Task.Wait()是一个同步方法,用于多线程中阻塞等待。

在那个「同步方法中调用异步方法」的文章中,用Task.Wait()来实现同步方法中调用异步方法,这个用法本身就是错误的。异步不是多线程,而且在多线程中,多个Task.Wait()使用也会死锁,也有解决和避免死锁的一整套方式。

再说一遍:Task.Wait()是一个同步方法,用于多线程中阻塞等待,不是实现同步方法中调用异步方法的实现方式。

说回await。字面意思,也好像是等待。是真的吗?

并不是,await不完全是等待的意思。

在异步中,await表达的意思是:当前线程/方法中,await引导的方法出结果前,跳出当前线程/方法,从调用当前线程/方法的位置,去执行其它可能执行的线程/方法,并在引导的方法出结果后,把运行点拉回到当前位置继续执行;直到遇到下一个await,或线程/方法完成返回,跳回去刚才外部最后执行的位置继续执行。

有点绕,还是看代码:

  static async Task Main(string[] args){
1     Console.WriteLine("Async proccess - start");2     Console.WriteLine("Async proccess - enter Func1");
3     func1();
4     Console.WriteLine("Async proccess - out Func1");5     Console.WriteLine("Async proccess - done");6         Thread.Sleep(2000);7     Console.WriteLine("Main proccess - done");8    Console.ReadKey();}private static async Task func1(){
9     Console.WriteLine("Func1 proccess - start");
10    await Task.Run(() => Thread.Sleep(1000));
11    Console.WriteLine("Func1 proccess - end");}

这个代码,执行时是这样的:顺序执行1、2、3,进到func1,执行9、10,到10时,有await,所以跳出,执行4、5、6。而6是一个长时等待,在等待的过程中,func1的10运行完成,运行点跳回10,执行11并结束方法,再回到6等待,结束等待后继续执行7、8结束。

我们看一下结果:

Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Async proccess - out Func1
Async proccess - done
Func1 proccess - end
Main proccess - done

映证了这样的次序。

在这个例子中,await在控制异步的执行次序。那为什么要用等待这么个词呢?是因为await确实有等待结果的含义。

这是await的第二层意思。

五、await的第二层意思:等待拿到结果

await确实有等待的含义。等什么?等异步的运行结果。

看代码:

static async Task Main(string[] args)
{Console.WriteLine("Async proccess - start");Console.WriteLine("Async proccess - enter Func1");Task<int> f = func1();Console.WriteLine("Async proccess - out Func1");Console.WriteLine("Async proccess - done");int result = await f;Console.WriteLine("Main proccess - done");Console.ReadKey();
}private static async Task<int> func1()
{Console.WriteLine("Func1 proccess - start");await Task.Run(() => Thread.Sleep(1000));Console.WriteLine("Func1 proccess - end");return 5;
}

比较一下这段代码和上一节的代码,很容易搞清楚执行过程。

这个代码,完成了这样一个需求:我们需要使用func1方法的返回值。我们可以提前去执行这个方法,而不急于拿到方法的返回值,直到我们需要使用时,再用await去获取到这个返回值去使用。

这才是异步对于我们真正的用处。对于一些耗时的IO或类似的操作,我们可以提前调用,让程序可以利用执行过程中的空闲时间来完成这个操作。等到我们需要这个操作的结果用于后续的执行时,我们await这个结果。这时候,如果await的方法已经执行完成,那我们可以马上得到结果;如果没有完成,则程序将继续执行这个方法直到得到结果。

六、同步方法中调用异步

正确的方法只有一个:

func1().GetAwaiter().GetResult();

这其实就是await的一个变形。

(全文完)

喜欢就来个三连,让更多人因你而受益

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

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

相关文章

[Spring5]IOC容器_Bean管理注解方式_注入属性@Autowired_@Qualified_@Resource_@Value

基于注解方式实现属性注入 &#xff08;1&#xff09;AutoWired&#xff1a;根据属性类型进行自动装配 第一步 把service和dao对象创建&#xff0c;在service和dao类添加创建对象注解 第二步 在service注入dao对象&#xff0c;在service类添加dao类型属性&#xff0c;在属性…

.Net Core in Docker极简入门(上篇)

点击上方蓝字"小黑在哪里"关注我吧环境准备Docker基础概念Docker基础命令Docker命令实践构建Docker镜像Dockerfilebulid & run前言Docker 是一个开源的应用容器引擎&#xff0c;它十分火热&#xff0c;如今几乎成为了后端开发人员必须掌握的一项技能。即使你在生…

ASP.NET Core中的内存缓存

ASP.NET Core中的内存中缓存让我们看看如何通过缓存优化ASP.NET Core应用程序性能我相信&#xff0c;在我们的工作中&#xff0c;每个人都收到来自客户的请求或来自我们应用程序用户的反馈&#xff0c;以提高响应速度。如果在编写代码时仅使用最佳实践还不够&#xff0c;那么我…

[Spring5]IOC容器_Bean管理注解方式_完全注解开发

完全注解开发 &#xff08;1&#xff09;创建配置类&#xff0c;替代xml配置文件 package com.atguigu.spring.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;Configuration //作为配置…

ASP.NET Core在 .NET 5 Preview 7的更新

.NET 5 Preview 7现在可以用了&#xff0c;可以进行评估了。这是此版本中的新增功能&#xff1a;Blazor WebAssembly应用程序现在针对.NET 5更新了Blazor WebAssembly的调试要求Blazor的可访问性改进Blazor的性能改进证书认证性能改进发送HTTP/2 PING帧支持Kestrel套接字传输中…

[Spring5]AOP底层原理

AOP底层原理 1.AOP底层使用动态代理 &#xff08;1&#xff09;有两种情况动态代理 第一种 有接口的情况&#xff0c;使用JDK动态代理 a.创建接口实现类代理对象&#xff0c;增强类的方法 第二种 没有接口的情况&#xff0c;使用CGLIB动态代理 a.创建子类的代理对象&#…

Hangfire定时触发作业,好像很简单?

【导读】本节我们继续稍微详细讲讲在我没有详细了解源码的前提下来探讨通过Hangfire定时触发作业有哪些需要注意的事项间隔时间内执行作业举个栗子&#xff0c;每隔10秒监控系统CPU&#xff0c;若CPU飙高&#xff08;根据实际业务定义百分比&#xff09;则在控制台打印输出&…

[Swagger2]Swagger简介

Swagger简介 前后端分离 前端 -> 前端控制层、视图层 后端 -> 后端控制层、服务层、数据访问层 前后端通过API进行交互 前后端相对独立且松耦合 产生的问题 前后端集成&#xff0c;前端或者后端无法做到“及时协商&#xff0c;尽早解决”&#xff0c;最终导致问题…

五分钟快速搭建Serverless免费邮件服务

1. 引言本文将带你快速基于 Azure Function 和 SendGrid 构建一个免费的Serverless&#xff08;无服务器&#xff09;的邮件发送服务&#xff0c;让你感受下Serverless的强大之处。该服务可以每月免费发送2,5000封&#xff0c;这是完全白嫖啊&#xff0c;感兴趣的&#xff0c;赶…

[Swagger2]SpringBoot集成Swagger

SpringBoot集成Swagger 引入依赖 <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</…

IdentityServer4 4.x版本 配置Scope的正确姿势

点击上方蓝字"小黑在哪里"关注我吧前言IdentityServer4 是为ASP.NET Core系列量身打造的一款基于 OpenID Connect 和 OAuth 2.0 认证的框架IdentityServer4官方文档&#xff1a;https://identityserver4.readthedocs.io/看这篇文章前默认你对IdentityServer4 已经有一…

[Swagger2]配置Swagger

配置Swaggr 1、Swagger实例Bean是Docket&#xff0c;所以通过配置Docket实例来配置Swaggger。 package com.xxxx.swagger2.config;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.doc…

.Net Core微服务入门全纪录(完结)——Ocelot与Swagger

点击上方蓝字"小黑在哪里"关注我吧前言上一篇【.Net Core微服务入门全纪录&#xff08;八&#xff09;——Docker Compose与容器网络】完成了docker-compose.yml文件的编写&#xff0c;最后使用docker compose的一个up指令即可在docker中运行整个复杂的环境。本篇简单…

[Swagger2]Swaggr配置扫描接口配置Swagger开关

Swagger配置扫描接口 1、构建Docket时通过select()方法配置怎么扫描接口。 Bean public Docket docket() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()// 通过.select()方法&#xff0c;去配置扫描接口,RequestHandlerSelectors配置如何扫描…

最全.Net学习资料库上线,今日可免费下载各类资源!(附百度云链接)

送资料送资料1 适合学习者&#xff1a;0-10年.Net开发人员2 更新时间&#xff1a;2020年7月24日3 在哪领取&#xff1a;文末扫码免费领取4 包含课程&#xff1a;零基础就业必修/高级开发必修/架构师必修5 配套资料&#xff1a;视频配套源码/最新面试题合集/最新技术书/安装包你…

[Swagger2]拓展:其他皮肤

拓展&#xff1a;其他皮肤 我们可以导入不同的包实现不同的皮肤定义&#xff1a; 1、默认的 访问 http://localhost:8080/swagger-ui.html <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><ve…

如何基于 DDD 构建微服务?

本文将讨论微服务与 DDD 涉及到的概念、策划和设计方法&#xff0c;并且尝试将一个单体应用拆分成多个基于 DDD 的微服务。微服务的定义微服务中的“微”虽然表示服务的规模&#xff0c;但它并不是使应用程序成为微服务的唯一标准。当团队转向基于微服务的架构时&#xff0c;他…

.NET Core ResponseCache【缓存篇(一)】

一、前言源码1、最近一直在看项目性能优化方式&#xff0c;俗话说的好项目优化第一步那当然是添加缓存&#xff0c;我们的项目之所以卡的和鬼一样&#xff0c;要么就是你的代码循环查询数据库&#xff08;这个之前在我们的项目中经常出现&#xff0c;现在慢慢在修正&#xff09…

[Swagger2]分组和接口注释及小结

分组和接口注释及小结 配置API分组 1、如果没有配置分组&#xff0c;默认是default。通过groupName()方法即可配置分组&#xff1a; Bean public Docket docket(Environment environment) {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).groupName(&qu…

Asp.Net Core 中的“虚拟目录”

写在前面现在部署Asp.Net Core应用已经不再限制于Windows的IIS上&#xff0c;更多的是Docker容器、各种反向代理来部署。也有少部分用IIS部署的&#xff0c;IIS部署确实是又快又简单&#xff0c;图形化操作三下五除二就可以发布好一个系统了。在过去Asp.Net MVC 项目部署的时候…