async/await的特殊的地方
一:async如果是用于方法声明里,那么要求这个方法的返回值必须是Task、Task<TResult>、void这三种,而且await出现的地方要求其所在的方法必须是async修饰的方法;
不过void的最好不要用,如果说方法没有返回值用Task,有返回值用Task<TResult>;
先看两段代码:
①
static void Main(string[] args)
{
var task = FooAsync();
// Result的get方法会阻塞当前线程直到task执行完毕
Console.WriteLine(task.Result);
Console.WriteLine("Hello World!");
} static async Task<int> FooAsync()
{
// await task对象可以直接获取task对象执行完毕的返回值,因此await会将Task.Run(..)作为同步块;
int result = await Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕");
return 8;
});
return result;
}
②
static void Main(string[] args)
{
var task = FooAsync();
// Result的get方法会阻塞当前线程直到task执行完毕
Console.WriteLine(task.Result);
Console.WriteLine("Hello World!");
}
// TODO 注意,这个地方去掉了async和里面的await
static Task<int> FooAsync()
{
// 这时候不能用int作为变量类型了
Task<int> result = Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕");
return 8;
});
return result;
}
对于上面的async和await的写法,那么async和await关键字其实可以说一点用处也没有,直接通过Task就能达到完全一模一样的效果;
接着来看另一种写法:
static void Main(string[] args)
{
var task = FooAsync();
// Result的get方法会阻塞当前线程直到task执行完毕
Console.WriteLine(task.Result);
Console.WriteLine("Hello World!");
} /**
* 注意,Task是后台任务(由后台线程执行),而Java的Task,或者说Java默认的线程创建工厂创建的线程是前台线程
*/
static async Task<string> FooAsync()
{
// 这时候不能用int作为变量类型了
Task<int> result = Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕");
return 8;
});
int resultValue = await result;
return "333N" + resultValue; // TODO 注意这里多了转换结果的步骤,用async+await写这种需求很方便,如果只用Task那么就得把Task.Run(...)提升到外部调用的地方才能方便的实现这个功能;
}
这种写法只用Task<TResult>就不容易实现了,至少写起来要多蛮多步骤(需要再手动写个Task<string>对象然后用lambda表达式将await出来的结果转换为string返回出来,然后返回这个new出来的Task<string>对象);
然后再看另外一种写法
static void Main(string[] args)
{
var watch1 = Stopwatch.StartNew();
var watch2 = Stopwatch.StartNew();
Console.WriteLine("Begin");
var task = FooAsync();
watch1.Stop(); Console.WriteLine($"#耗时{watch1.ElapsedMilliseconds}"); // Result的get方法会阻塞当前线程直到task执行完毕
// 这个如果注释掉,那么FooAsync则只阻塞主线程2秒左右,如果不注释下面的代码,那么上面的FooAsync()会先阻塞主线程
//Console.WriteLine(task.Result);
watch2.Stop(); Console.WriteLine($"耗时{watch2.ElapsedMilliseconds}"); Console.WriteLine("End");
} /**
* 注意,Task是后台任务(由后台线程执行),而Java的Task,或者说Java默认的线程创建工厂创建的线程是前台线程(可配置)
*/
static async Task<int> FooAsync()
{
Thread.Sleep(2000); // 这一个会阻塞调用者
Console.WriteLine($"{nameof(FooAsync)}方法先同步耗时2秒");
// 这时候不能用int作为变量类型了
var result = Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕");
return 8;
});
Thread.Sleep(2000); // 重要,这一块不一定会阻塞调用方(await放在Task.Run那里)
Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗时2秒");
// 注意,如果这个await放到上面的Task.Run(..)那里会令这一块的逻辑很奇怪,而且貌似执行是有问题的,都不知道它执行完毕没有
return await result;
}
其实仔细一比较,async和await是可以不需要的,用Task<TResult>就完全可以实现,只不过有部分写法会稍微麻烦一点点而已;await task;就可以用task.Result来代替;
async加await其实就是实现这样一个功能:
有async的方法,是在告诉编译器这个方法内部可能存在异步调用,如Task.Run(...);
然后内部对这个异步调用进行一个await(没有也行但是就失去了async和await带来的糖);
这个糖的最重要的提现在于外部调用这个方法时,如这个方法叫FooAsync(),调用过程为:
var task = FooAsync();
如果接下来的代码没有类似task.Wait()或task.Result这样的等待操作,那么FooAsync()里面的异步部分对于调用者也是异步的(包括await后面的全部代码对于调用者都是异步的,这点很重要,如var result = await Task.Run(...);Thread.Sleep(2000);这个Sleep不会阻塞调用者);
async/await其实是一种类似模板类的技术,有async且内部有await的方法是一种具备不同编译情况的方法(怎么编译要看怎么调用及使用具有async的方法,如调用者有task.Result是一种使用模式,没有task.Result或没有await task或task.Wait..之类的又是另一种模式);
static void Main(string[] args)
{
// async/await其实是一种类似模板类的技术,有async且内部有await的方法是一种具备不同编译情况的方法;
var watch1 = Stopwatch.StartNew();
var watch2 = Stopwatch.StartNew();
Console.WriteLine("Begin");
var task = FooAsync();
watch1.Stop(); Console.WriteLine($"#耗时{watch1.ElapsedMilliseconds}"); // Result的get方法会阻塞当前线程直到task执行完毕
// 这个如果注释掉,那么FooAsync则只阻塞主线程2秒左右,如果不注释下面的代码,那么上面的FooAsync()会先阻塞主线程
//Console.WriteLine(task.Result);
watch2.Stop(); Console.WriteLine($"耗时{watch2.ElapsedMilliseconds}"); Console.WriteLine("End");
// 这里可以通过输出End后立刻按回车或等几秒后按回车进行测试;
Console.ReadKey();
} /**
* 注意,Task是后台任务(由后台线程执行),而Java的Task,或者说Java默认的线程创建工厂创建的线程是前台线程(可配置)
* 甚至编译器将FooAsync()方法编译成了FooAsync`1()和FooAsync`2()两个子方法,然后看调用者是否涉及到同步FooAsync方法结果的操作,有则编译器给调用方调用的是FooAsync`1(),没有则是用的FooAsync`2()方法
*/
static async Task<int> FooAsync()
{
Thread.Sleep(2000); // 这一个会阻塞调用者
Console.WriteLine($"{nameof(FooAsync)}方法先同步耗时2秒");
// 注意,await之后的所有代码是否阻塞调用者取决于调用者怎么用这个方法,如果调用者是await FooAsync(),那么这里之后的所有代码都是同步的
// TODO 所以async和await和Task<TResult>相比多了一个类似开关一样的东西,但是这个开关是编译阶段确定的,有点像模板类用的时候必须给出具体的类型,而这里是具体的用法;
// TODO 编译时如果调用者有task.Result之类的wait操作,那么【编译器】就将FooAsync整个当成一个同步方法,如果没有相关的操作,编译器就将这部分及其后面的代码作为异步方法处理【应该是通过闭包形成一个新的Task对象】
// TODO 这里往下的代码,如果编译器发现调用方不需要同步,那么编译器底层是通过再开一个Task<int>将result和下面的Thread.Sleep(4000)等代码用闭包再包一层返回这个task对象;
var result = await Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕");
return 8;
});
// TODO 注意,这一块是在上面的result执行完后才执行的,即上面先输出 FooAsync方法耗时3秒执行完毕 然后等待4秒后再输出 ###FooAsync方法先同步耗时4秒
Thread.Sleep(4000); // 重要,这一块不一定会阻塞调用方(await放在Task.Run那里),这一块是否阻塞调用者看调用者是否需要对返回的Task对象进行wait,且是编译时决定的;
Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗时4秒");
// 注意,如果这个await放到上面的Task.Run(..)那里会令这一块的逻辑很奇怪,而且貌似执行是有问题的,都不知道它执行完毕没有
return result;
}
来看进一步的测试:
static void Main(string[] args)
{
// async/await其实是一种类似模板类的技术,有async且内部有await的方法是一种具备不同编译情况的方法;
var watch1 = Stopwatch.StartNew();
var watch2 = Stopwatch.StartNew();
Console.WriteLine($"Begin,threadId:{Thread.CurrentThread.ManagedThreadId}");
var task = FooAsync();
watch1.Stop(); Console.WriteLine($"#耗时{watch1.ElapsedMilliseconds}"); // Result的get方法会阻塞当前线程直到task执行完毕
// 这个如果注释掉,那么FooAsync则只阻塞主线程2秒左右,如果不注释下面的代码,那么上面的FooAsync()会先阻塞主线程
//Console.WriteLine(task.Result); // TODO flagN
watch2.Stop(); Console.WriteLine($"耗时{watch2.ElapsedMilliseconds}"); Console.WriteLine("End");
// 这里可以通过输出End后立刻按回车或等几秒后按回车进行测试;
Console.ReadKey();
} /**
* 注意,Task是后台任务(由后台线程执行),而Java的Task,或者说Java默认的线程创建工厂创建的线程是前台线程(可配置)
*/
static async Task<int> FooAsync()
{
Thread.Sleep(2000); // 这一个会阻塞调用者
Console.WriteLine($"{nameof(FooAsync)}方法先同步耗时2秒");
// 注意,await之后的所有代码是否阻塞调用者取决于调用者怎么用这个方法,如果调用者是await FooAsync(),那么这里之后的所有代码都是同步的
// TODO 所以async和await和Task<TResult>相比多了一个类似开关一样的东西,但是这个开关是编译阶段确定的,有点像模板类用的时候必须给出具体的类型,而这里是具体的用法;
// TODO 编译时如果调用者有task.Result之类的wait操作,那么【编译器】就将FooAsync整个当成一个同步方法,如果没有相关的操作,编译器就将这部分及其后面的代码作为异步方法处理【应该是通过闭包形成一个新的Task对象】
// TODO 这里往下的代码,如果编译器发现调用方不需要同步,那么编译器底层是通过再开一个Task<int>将result和下面的Thread.Sleep(4000)等代码用闭包再包一层返回这个task对象;
var result = await Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕,threadId:{Thread.CurrentThread.ManagedThreadId}");
return 8;
});
// TODO 注意,这一块是在上面的result执行完后才执行的,即上面先输出 FooAsync方法耗时3秒执行完毕 然后等待4秒后再输出 ###FooAsync方法先同步耗时4秒
Thread.Sleep(4000); // 重要,这一块不一定会阻塞调用方(await放在Task.Run那里),这一块是否阻塞调用者看调用者是否需要对返回的Task对象进行wait,且是编译时决定的;
Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗时4秒,threadIdNNN:{Thread.CurrentThread.ManagedThreadId}");
return result;
}
经过测试,flagN行无论是否注释,主线程的threadId和后面Task.Run及后面的threadIdNNN的线程id都是不同的,但是后面两个的线程id是一致的,因此编译器底层不是生成了FooAsync`1和FooAsync`2两个方法,而是将await及后面的代码合并为
一块,即FooAsync其实最终是变成这样子的代码(不一定百分百就是这个样子,但是可以解释测试结果):
static async Task<int> FooAsync()
{
Thread.Sleep(2000); // 这一个会阻塞调用者
Console.WriteLine($"{nameof(FooAsync)}方法先同步耗时2秒");
// 注意,await之后的所有代码是否阻塞调用者取决于调用者怎么用这个方法,如果调用者是await FooAsync(),那么这里之后的所有代码都是同步的
// TODO 所以async和await和Task<TResult>相比多了一个类似开关一样的东西,但是这个开关是编译阶段确定的,有点像模板类用的时候必须给出具体的类型,而这里是具体的用法;
// TODO 编译时如果调用者有task.Result之类的wait操作,那么【编译器】就将FooAsync整个当成一个同步方法,如果没有相关的操作,编译器就将这部分及其后面的代码作为异步方法处理【应该是通过闭包形成一个新的Task对象】
// TODO 这里往下的代码,如果编译器发现调用方不需要同步,那么编译器底层是通过再开一个Task<int>将result和下面的Thread.Sleep(4000)等代码用闭包再包一层返回这个task对象;
var result = Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕,threadId:{Thread.CurrentThread.ManagedThreadId}");
var tmp = 8; // TODO 注意,这一块是在上面的result执行完后才执行的,即上面先输出 FooAsync方法耗时3秒执行完毕 然后等待4秒后再输出 ###FooAsync方法先同步耗时4秒
Thread.Sleep(4000); // 重要,这一块不一定会阻塞调用方(await放在Task.Run那里),这一块是否阻塞调用者看调用者是否需要对返回的Task对象进行wait,且是编译时决定的;
Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗时4秒,threadIdNNN:{Thread.CurrentThread.ManagedThreadId}"); return 8;
});
return result;
}
这个result不会阻塞调用者;
async/await的特殊的地方的更多相关文章
- [C#] 走进异步编程的世界 - 开始接触 async/await
走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...
- 实际案例:在现有代码中通过async/await实现并行
一项新技术或者一个新特性,只有你用它解决实际问题后,才能真正体会到它的魅力,真正理解它.也期待大家能够多分享解一些解决实际问题的内容. 在我们遭遇“黑色30秒”问题的过程中,切身体会到了异步的巨大作用 ...
- [C#] .NET4.0中使用4.5中的 async/await 功能实现异
好东西需要分享 原文出自:http://www.itnose.net/detail/6091186.html 在.NET Framework 4.5中添加了新的异步操作库,但是在.NET Framew ...
- 走进异步编程的世界 - 开始接触 async/await
[C#] 走进异步编程的世界 - 开始接触 async/await 走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async ...
- async/await 异步编程(转载)
转载地址:http://www.cnblogs.com/teroy/p/4015461.html 前言 最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入 ...
- Python PEP 492 中文翻译——协程与async/await语法
原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...
- C# 中 async/await 调用传统 Begin/End 异步方法
最近在改进园子的图片上传程序,希望实现用户上传图片时同时将图片文件保存在三个地方:1)服务器本地硬盘:2)又拍云:3)阿里云OSS.并且在保存时使用异步操作. 对于异步保存到本地硬盘,只需用 Stea ...
- [C#] 走进异步编程的世界 - 开始接触 async/await(转)
原文链接:http://www.cnblogs.com/liqingwen/p/5831951.html 走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 ...
- async/await 异步编程
前言 最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入的,由于之前对于异步编程不是很了解,所以花费了一些时间学习一下相关的知识,并整理成这篇博客,如果在 ...
随机推荐
- 把已经安装到C盘的软件完美移动到D盘
背景信息 今天早上在安装软件的时候发现C盘爆满,只剩下最后10G了.而我要安装的玩意儿必须装到C盘. 靠清理垃圾文件来解决并不是一个好方法,实际上通常垃圾文件占用很少,而且就算清理了,也还会再出现. ...
- vue+element 实现在表格内插入其他组件,每行数据独立存储
使用 v-slot row代表当前行
- 用户名、密码等15个常用的js正则表达式
本文收集整理了15个常用的javaScript正则表达式,其中包括用户名.密码强度.整数.数字.电子邮件地址(Email).手机号码.身份证号.URL地址. IPv4地址. 十六进制颜色. 日期. Q ...
- 8-安装Kafka
1.解压 tar -zxvf kafka_2.11-0.9.0.1.tar -C /opt/app/ 2.改权限 chown -R hadoop:hadoop /opt/app/ 3.修改配置文件 c ...
- php解析excel文件
public static function getStaffByXlsx($path) { /*dirname(__file__): 当前代码所在的目录,$path: ”/文件名“ */ $PHPR ...
- Variables多种表达
Variables:TF基础数据之一,常用于变量的训练...重要性刚学TF就知道了 1.tf.Variable() tf.Variable(initial_value=None, trainable= ...
- Python模拟登陆TAPD
因为在wiki中未找到需要的数据,查询也很迷,打算用python登录tapd抓取所需项目下的wiki数据,方便查找. 2018-9-30 19:12:44 几步走 模拟登录tapd 抓取wiki页左侧 ...
- WPF程序 双击exe自动申请“以管理员方式运行”权限
实现方式: 在 xxx.exe 目录下包含其对应的清单文件(xxx.exe.manifest, 由 VS 编译时自动生成): 用记事本打开 manifest 文件,将文件中的项:<request ...
- Spring系列之Spring常用注解总结 转载
Spring系列之Spring常用注解总结 传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做有两个缺点:1.如果所有的内容都配置在.xml文件中,那么.x ...
- AOP方法增强自身内部方法调用无效 SpringCache 例子
开启注解@EnableCaChing,配置CacheManager,结合注解@Cacheable,@CacheEvit,@CachePut对数据进行缓存操作 缺点:内部调用,非Public方法上使用注 ...