.NET(C#):await返回Task的async方法
众所周知,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代码做如下警告:

提示: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的测试,呵呵。www.2cto.com
或许你会问,为什么要用这样的方式去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可以这样全靠的是编译器)。
摘自 Mgen
.NET(C#):await返回Task的async方法的更多相关文章
- 使用Unity拦截一个返回Task的方法
目标 主要是想为服务方法注入公用的异常处理代码,从而使得业务代码简洁.本人使用Unity.Interception主键来达到这个目标.由于希望默认就执行拦截,所以使用了虚方法拦截器.要实现拦截,需要实 ...
- async方法:async+await
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 异步编程系列第04章 编写Async方法
p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...
- 水火难容:同步方法调用async方法引发的ASP.NET应用程序崩溃
之前只知道在同步方法中调用异步(async)方法时,如果用.Result等待调用结果,会造成线程死锁(deadlock).自己也吃过这个苦头,详见等到花儿也谢了的await. 昨天一个偶然的情况,造成 ...
- 从Thread,ThreadPool,Task, 到async await 的基本使用方法解读
记得很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题 ...
- Thread,ThreadPool,Task, 到async await 的基本使用方法和理解
很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧. ...
- 异步方法的意义何在,Async和await以及Task的爱恨情仇,还有多线程那一家子。
前两天刚感受了下泛型接口的in和out,昨天就开始感受神奇的异步方法Async/await,当然顺路也看了眼多线程那几个.其实多线程异步相关的类单个用法和理解都不算困难,但是异步方法Async/awa ...
- C#中 Thread,Task,Async/Await,IAsyncResult 的那些事儿!
说起异步,Thread,Task,async/await,IAsyncResult 这些东西肯定是绕不开的,今天就来依次聊聊他们 1.线程(Thread) 多线程的意义在于一个应用程序中,有多个执行部 ...
- C#多线程和异步(二)——Task和async/await详解
一.什么是异步 同步和异步主要用于修饰方法.当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法:当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务 ...
随机推荐
- [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(1)
前面俩种实现中,很多内部细节都无法知道,微软的框架也是为了屏蔽具体实现,只让我们关注接口.但是人都是充满好奇的,依赖注入到底是怎么实现的呢? 微软又有怎样的实现呢?下面就为大家一一呈现(说实话,代码真 ...
- Web API接口设计经验总结
在Web API接口的开发过程中,我们可能会碰到各种各样的问题,我在前面两篇随笔<Web API应用架构在Winform混合框架中的应用(1)>.<Web API应用架构在Winfo ...
- 从C#到Objective-C,循序渐进学习苹果开发(1)--准备开发账号和开发环境
本随笔系列主要介绍从一个Windows平台从事C#开发到Mac平台苹果开发的一系列感想和体验历程,本系列文章是在起步阶段逐步积累的,希望带给大家更好,更真实的转换历程体验,因为一旦方方面面都精通了,也 ...
- CentOS 配置防火墙操作实例(启、停、开、闭端口)
防火墙的基本操作命令 查询防火墙状态:[root@localhost ~]# service iptables status<回车> 停止防火墙:[root@localhost ~]# ...
- 【原创】asp.net导出word 检索 COM 类工厂中 CLSID 为 {000209FF-0000-0000-C000-000000000046} 的组件失败,原因是出现以下错误: 8000401a
我的服务器:windows server 2008(64位)+microsoft office 2007 企业版 业务:网站导出应聘word简历. 出现以下错误: 检索 COM 类工厂中 CLSID ...
- Oracle--(Hierarchical Queries)层级查询
内容来自: Oracle® Database SQL Language Reference 11g Release 2 (11.2) E41084-03. empolyees表来自hr方案,wareh ...
- volcanol_Linux_ 问题汇总系列_4_Thinkpad_E40_0578MDC_在Fedora 13 Linux(FC13)中如何安装无线网卡驱动
今天晚上,我突然想在自己到笔记本上安装linux系统,因为我自己第一次接触到的linux是红帽支持到Fedora Core 4,所以一直最中意的linux 发行版本是FC系列,同时由于FC 15以后到 ...
- LBS数据分析:使用地图展示统计数据——麻点图与麻数图
作为一个LBS的APP,都获得了用户经纬度,也都使用了友盟统计.google ana等等统计分析系统,不过没有地图展示功能,不能进行直观的展示. 友盟统计.google ana等系统是总体数据统计,无 ...
- Cytoscape.js – 用于数据分析和可视化的交互图形库
Cytoscape.js 是一个开源的 JavaScript 图形库,您可以使用 Cytoscape.js 进行数据分析和可视化.Cytoscape.js 可以轻松的继承到你的网站或者 Web 应用 ...
- 优秀的 HTML5 实战教程,提升你的综合开发能力
HTML5 作为下一代网站开发技术,无论你是一个 Web 开发人员或者想探索新的平台的游戏开发者,都值得去研究.借助尖端功能,技术和 API,HTML5 允许你创建响应性.创新性.互动性以及令人惊叹的 ...