NET(C#):await返回Task的async方法

一.  FrameWork 4.0之前的线程世界    

    在.NET FrameWork 4.0之前,如果我们使用线程。一般有以下几种方式:

  • 使用System.Threading.Thread 类,调用实例方法Start()开启一个新线程,调用Abort()方法来提前终止线程。
  • 使用System.Threading.ThreadPool类,调用静态方法QueueUserWorkItem(),将方法放入线程池队列,线程池来控制调用。
  • 使用BeginInvoke,EndInvoke,BeginRead,EnRead,BeginWrite,EndWrite等一系列的异步方法。
  • 使用System.ComponentModel.BackgroundWorker控件,调用实例方法RunWorkerAsync(),开启一个新线程。 

二.  .Net 传统异步编程概述 

  • 异步编程模型 (APM),在该模型中异步操作由一对 Begin/End 方法(如 FileStream.BeginRead 和 Stream.EndRead)表示。
  • 基于事件的异步模式 (EAP),在该模式中异步操作由名为“操作名称Async”和“操作名称Completed”的方法/事件对(例如 WebClient.DownloadStringAsync 和 WebClient.DownloadStringCompleted)表示。 (EAP 是在 .NET Framework 2.0 版中引入的,在silverlight或者wpf变成中经常用到)。

三.  Task 的优点以及功能   

  • 在任务启动后,可以随时以任务延续的形式注册回调。
  • 通过使用 ContinueWhenAll 和 ContinueWhenAny 方法或者 WaitAll 方法或 WaitAny 方法,协调多个为了响应 Begin_ 方法而执行的操作。
  • 在同一 Task 对象中封装异步 I/O 绑定和计算绑定操作。
  • 监视 Task 对象的状态。
  • 使用 TaskCompletionSource 将操作的状态封送到 Task 对象。

 

众所周知,async方法只可以返回void,Task和Task<T>。

 

对于返回void的async方法,它并不是awaitable,所以其他方法不能用await方法来调用它,而返回Task的async方法则可以。

 

那么当async方法返回Task后,接着await,那被await的Task是一个什么概念?是async方法中第一个被await的Task?不,它代表目标async方法的全部执行,其中包括被await分割的连接Task,但是不包括非await造成的多线程执行。

 

如下代码,在doo是一个返回Task的async方法,然后在另一个方法test中await调用doo,然后在Main方法中调用test(由于Main方法不允许加async,所以需要另外加一个async方法来使用await)

static void Main(string[] args)

{

    test();

    log("Main:调用test后");

    Thread.Sleep(Timeout.Infinite);

}

 

//Main方法不允许加async,所以我们用这个方法使用await

static async void test()

{

    log("test: await之前");

    await doo();

    log("test: await之后");

}

 

//返回Task的async方法

static async Task doo()

{

    log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 1; }));

    log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 2; }));

    log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 3; }));

    Thread.Sleep(1000);

    Console.WriteLine("doo中在Task外的Thread.Sleep执行完毕");

}

 

//输出方法:显示当前线程的ManagedThreadId

static void log(string msg)

{

    Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, msg);

}

 

上面代码会输出:

1: test: await之前

1: Main:调用test后

3: Task

3: doo: Task结果:1

4: Task

4: doo: Task结果:2

3: Task

3: doo: Task结果:3

doo中在Task外的Thread.Sleep执行完毕

3: test: await之后

 

前两句简单,调用test方法,await后的内容会被加在目标Task的后面,然后test马上返回,于是输出“Main:调用test后”,同时他们都是在主线程中执行的,所以ManagedThreadId都是1。

 

接着后面就是另一个Task的执行(当然在另一个线程,也是test方法中await的目标Task)。这个所谓的Task就是doo方法的全部执行。所以doo中三个顺序执行的Task(通过await一个一个连接)依次执行,所以Task输出结果1,2,3。第一个Task的ManagedThreadId是3,第二个是4,第三个又是3,原因是Task的内部执行使用了CLR的线程池,所以线程得到了重复利用。

 

接着doo方法还没有完,最后一个await造成doo方法后面的代码在这个await针对的Task执行后继续执行,于是输出:doo中Task外的Thread.Sleep执行完毕。

 

最后当doo彻底执行完test的await才结束,所以最后一行输出:test:await之后。

 

 

上面我说过:被await的async方法返回的Task代表“目标async方法的全部执行,其中包括被await分割的连接Task,但是不包括非await造成的多线程执行”。

所以如果把返回Task的async方法(也就是上例中的doo方法)改成这样:

//返回Task的async方法

static async Task doo()

{

    log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 1; }));

    log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 2; }));

    log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 3; }));

 

    //不使用await:线程池多线程

    ThreadPool.QueueUserWorkItem(_ =>

        {

            Thread.Sleep(1000);

            Console.WriteLine("ThreadPool.QueueUserWorkItem");

        });

 

    //不使用await:Task多线程

    Task.Run(() =>

        {

            Thread.Sleep(1000);

            Console.WriteLine("Task.Run");

        });

}

 

我们加入了不用await的多线程执行,分别使用ThreadPool和Task,整个程序会输出这样的结果:

1: test: await之前

1: Main:调用test后

3: Task

3: doo: Task结果:1

4: Task

4: doo: Task结果:2

3: Task

3: doo: Task结果:3

3: test: await之后

Task.Run

ThreadPool.QueueUserWorkItem

 

不使用await的多线程完全脱离了test方法中await的Task,是运行在test的await之后的。

 

另外Visual Studio会对Task.Run代码做如下警告:
id="iframe_0.5044486353473234" frameborder="0" scrolling="no" style="border-style:none;width:47px;">


 

提示:Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the ‘await’ operator to the result of the call.

就是说,如果不加await,当前方法会继续执行直到结束,不用管他,因为我们现在就是在做在async方法中不用await的测试,呵呵。

 

 

或许你会问,为什么要用这样的方式去await另一个async方法返回的Task呢?我们一直在讨论返回Task的async方法,我认为看一个返回Task<T>的async方法可以更好地解释这个问题。

 

下面我们把上面的代码改成相似的返回Task<int>的async方法执行,那么doo方法返回Task<T>,他把自己方法内3个awaited Task的结果统一相加,最后返回结果并作为自己返回的Task的结果。然后在test方法中输出doo返回的结果。

 

完整代码:

static void Main(string[] args)

{

    test();

    log("Main:调用test后");

    Thread.Sleep(Timeout.Infinite);

}

 

//Main方法不允许加async,所以我们用这个方法使用await

static async void test()

{

    log("test: await之前");

    Console.WriteLine("doo结果:{0}", await doo());

    log("test: await之后");

}

 

//返回Task的async方法

static async Task<int> doo()

{

    var res1 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task1执行"); return

    var res2 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task2执行"); return

    var res3 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task3执行"); return

 

    //不使用await:线程池多线程

    ThreadPool.QueueUserWorkItem(_ =>

        {

            Thread.Sleep(1000);

            Console.WriteLine("ThreadPool.QueueUserWorkItem");

        });

 

    //不使用await:Task多线程

    Task.Run(() =>

        {

            Thread.Sleep(1000);

            Console.WriteLine("Task.Run");

        });

 

    return res1 + res2 + res3;

}

 

//输出方法:显示当前线程的ManagedThreadId

static void log(string msg)

{

    Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, msg);

}

 

先看结果:

1: test: await之前

1: Main:调用test后

3: awaited Task1执行

4: awaited Task2执行

4: awaited Task3执行

doo结果:6

4: test: await之后

ThreadPool.QueueUserWorkItem

Task.Run

 

 

和上一个返回Task的例子一样,当在test方法中await doo方法返回的Task,doo内awaited Task都被先等了,而没有awaited的线程都并没有被等,这是为什么呢(也就是上面留下的那个问题)?下面用这个返回Task<int>的例子解释一下:

在test中await doo返回的Task,那么此时我们需要他的结果,而他的结果是需要自己方法内所包含的其他awaited结果,可以理解成被等的子结果。所以自己的结果需要其他的结果,那么等这个结果必须需要等那些被依赖的结果也出来。所以test方法await doo方法的结果会同样等待所有doo内的await,不会管其他doo内非await的多线程执行(当然从技术角度讲,也是不可能的,因为async/await可以这样全靠的是编译器)。

转载于:https://www.cnblogs.com/Jeely/p/10997338.html

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

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

相关文章

Nature评论:机器学习的物理启示录——隔壁的另一条机遇之道

来源&#xff1a;AI科技评论作者&#xff1a;Don编辑&#xff1a;青暮老话说&#xff1a;隔行不取利。但时过境迁&#xff0c;目前不管是娱乐圈还是学术界&#xff0c;跨界方可大红大紫。在娱乐圈&#xff0c;相声演员客串脱口秀&#xff0c;歌手跨界演员&#xff0c;赚的钵满盆…

操作系统——文件的逻辑结构

文章目录1.文件的逻辑结构的概念1.1 按照逻辑结构的文件分类1.2 无结构文件和有结构文件的区别2 顺序文件3 索引文件4.索引顺序表1.文件的逻辑结构的概念 1.1 按照逻辑结构的文件分类 1.2 无结构文件和有结构文件的区别 2 顺序文件 3 索引文件 4.索引顺序表

操作系统——文件目录

文章目录1.文件目录知识点2. 文件分配方式3.文件的存储空间管理4.文件的基本操作5.文件共享6.文件保护7.文件系统的层次结构1.文件目录知识点 2. 文件分配方式 3.文件的存储空间管理 4.文件的基本操作 5.文件共享 6.文件保护 7.文件系统的层次结构

我们不知道答案的125个科学问题(16)群体合作行为的演化

来源&#xff1a;张林科学网博客链接地址&#xff1a;http://blog.sciencenet.cn/blog-318012-1292142.html题记&#xff1a;离Science杂志提出125个问题到今天已经过去了16个年头&#xff0c;然而我这个125个问题的系列解读仅仅进行到第16个&#xff0c;想必这125个问题自己也…

Nature:大脑空间导航研究五十年

来源&#xff1a;集智俱乐部作者&#xff1a;Isabel I. C. Low & Lisa M. Giocomo译者&#xff1a;赵雨亭 审校&#xff1a;张澳 编辑&#xff1a;邓一雪 导语老马识途的故事被人们所熟知&#xff0c;但其识途的神经机制却尚待研究。1971年&#xff0c;研究人员首次发现&am…

操作系统——磁盘

文章目录1.磁盘的结构2.磁盘调度算法3.减少磁盘延时时间的方法4.磁盘管理1.磁盘的结构 2.磁盘调度算法 3.减少磁盘延时时间的方法 4.磁盘管理

建网站如何选择好用的网站源码程序

很多新手朋友第一次建网站时候&#xff0c;如何选择一款适合的网站源码是比较困惑的问题&#xff0c;选择一款好的网站源码可以节约大量时间和金钱&#xff0c;但是由于网站源码参差不齐&#xff0c;免费的&#xff0c;收费的&#xff0c;淘宝几元钱购买的&#xff0c;几万块钱…

操作系统——设备管理

文章目录1.I/O设备的基本概念与分类2.I/O控制器3.I/O控制方式4.I/O软件层次结构5.I/O核心子系统6.假脱机技术7.设备的分配和回收8.缓冲区管理1.I/O设备的基本概念与分类 2.I/O控制器 3.I/O控制方式 4.I/O软件层次结构 5.I/O核心子系统 6.假脱机技术 7.设备的分配和回收 8.缓冲区…

95页重磅报告:全面预测未来5年趋势

来源&#xff1a;中产财富分水岭将成为未来5年中国互联网的关键词&#xff0c;从浅水区向深水区过渡&#xff0c;引发竞争格局的强弱式转化。分水岭期不存在直道竞争&#xff0c;冷静和变化成为主旋律。版权申明&#xff1a;内容来源网络&#xff0c;版权归原创者所有。除非无法…

字符编码问题

字符编码 Windows默认支持的中文编码为gbk&#xff0c;gb&#xff0c;ANSILinux默认支持的中文编码为utf-8&#xff08;unicode&#xff09;这就要求我们有关Linux的中文编程要采用utf-8&#xff0c;而windows要采用ANSI等&#xff0c;否则会出现乱码。 当我们将Linux和windows…

数据库基础知识——数据库的相关概念

文章目录1.数据库的好处2.数据库相关概念3.数据库存储数据的特点1.数据库的好处 1.持久化数据到本地 2.可以实现结构化查询&#xff0c;方便管理2.数据库相关概念 1、DB&#xff1a;数据库&#xff0c;保存一组有组织的数据的容器 2、DBMS&#xff1a;数据库管理系统&#xf…

新发现为类脑计算机开辟了道路

来源&#xff1a;ScienceAI编辑&#xff1a;萝卜皮大型自旋霍尔纳米振荡器&#xff08;SHNO&#xff09;阵列的同步&#xff0c;是实现超快非常规计算的一种有吸引力的方法。然而&#xff0c;与阵列接口、调整其单个振荡器和提供内置存储器单元仍然存在巨大的难题。瑞典哥德堡大…

数据库基础知识——初始MySQL

文章目录1.MySQL服务的启动和停止2.MySQL服务的登录和退出3.MySQL的常见命令3.MySQL的语法规范4.SQL的语言分类1.MySQL服务的启动和停止 方式一&#xff1a;计算机——右击管理——服务 方式二&#xff1a;通过管理员身份运行 net start 服务名&#xff08;启动服务&#xff0…

Tomcat性能优化总结

层级优化&#xff01;&#xff08;JAVA_OPTS参数和主要元素的优化&#xff09; Tomcat大致元素架构&#xff1a;server--->service-->Engine-->Host-->Context Tomcat有可以使用三种协议:HTTP&#xff0c;AJP&#xff0c;HTTPS tomcat默认采用的BIO模型 tomcat的运…

互联网大脑如何产生“梦境“并形成元宇宙

作者&#xff1a;刘锋本文摘录自2019年中信出版社出版的《崛起的超级智能&#xff0c;互联网大脑如何影响科技未来》的第二章“10条规则&#xff1a;互联网大脑如何影响科技企业的命运”中的“第九条规则&#xff0c;互联网大脑梦境的构建带来产业升级”。这一节详细阐述了互联…

2022年人工智能领域发展七大趋势

来源&#xff1a;科技日报编辑&#xff1a;蒲蒲美国《福布斯》网站在近日的报道中指出&#xff0c;尽管目前很难想象机器自主决策所产生的影响&#xff0c;但可以肯定的是&#xff0c;当时光的车轮到达2022年时&#xff0c;人工智能领域新的突破和发展将继续拓宽我们的想象边界…

数据库基础知识——DQL语言(一)

文章目录1.基础查询2.条件查询3.排序查询4.常见函数4.1 单行函数4.1.1 字符函数4.1.2 数学函数4.1.3 日期函数4.1.4 流程控制函数4.1.5 其他函数4.2 分组函数/统计函数/聚合函数5.分组查询1.基础查询 语法&#xff1a; SELECT 要查询的东西 【FROM 表名】;#查询employees表中所…

Linux安装MariaDB(Mysql)和简单配置

安装MariaDB 安装命令yum -y install mariadb mariadb-server安装完成MariaDB&#xff0c;首先启动MariaDBsystemctl start mariadb设置开机启动systemctl enable mariadbMariaDB的相关简单配置mysql_secure_installation首先是设置密码&#xff0c;会提示先输入密码Enter curr…

数据库基础知识——DQL语言(二)

文章目录1.多表连接查询1.1 sql92语法1.1.1 等值连接1.1.2 sql92:非等值连接1.1.3 sql92:自连接1.2 sql99语法1.2.1 等值连接1.2.2 非等值连接1.2.3 内连接1.2.4 左右连接&#xff08;外连接&#xff09;2.子查询2.1 where或者having后面3.分页查询4.联合查询1.多表连接查询 笛…

周志华教授发表首届国际学习与推理联合大会IJCLR开场Keynote:探索从纯学习到学习+推理的AI...

周志华&#xff0c;毕业于南京大学&#xff0c;欧洲科学院外籍院士&#xff0c;国家杰出青年基金获得者&#xff0c;现任南京大学人工智能学院院长、南京大学计算机软件新技术国家重点实验室常务副主任、机器学习与数据挖掘研究所 (LAMDA)所长、人工智能教研室主任。2021年8月1…