1. 为什么会有/怎么解决: async/await的无限嵌套

public async Task<int> myFuncAsync1()
{
//some code
  int num = await getNumberFromDatabaseAsync(); //如果没有await那么async修饰的函数仍然是同步执行,失去意义
  return num;
} public async Task<string> myFuncAsync2()
{ 
  //some code
  int num = await myFuncAsync1(); //因为用await等待async函数,所以此函数也要标记为async
  string s = ""+ num.toString();
  return s;
}
public async Task<int> myFuncAsync3()
{
...
}
...

第一次遇到async/await是在做一个智能家居的网络控制程序上,为了不阻塞UI,老同事说把其中有的方法改成了async,让后端修改数据库的逻辑异步执行,返回操作结果之后再刷新UI,可是发现把一个函数标记成async之后,你就必须在这个函数里头有await的对象(通常也是一个异步函数)才能让async真的异步,否则即使标记async函数仍然会同步执行,那这个await的对象又一定要是一个async的函数,同理,刚才说的函数的母函数里头,他又需要在一个await修饰调用的这个函数才能等到结果,有了await母函数也要标记为async,那是不是无穷无尽了?

答案是否定的。

先说下为什么为引起这样的原因。正如上文提到的,一般是因为你想异步执行什么操作,归根结底就是异步的数据库操作或者调用异步API,比如调用 QueryAsync()或者httpGetAsync(),这些ORM或者API已经被分装成async的形式了,你为了等他们结束获取结果,需要await他们,而await关键字只能在async函数中使用,以此类推。。所以如果你看到一个async函数,一直向下查看的话,应该能看到最底层的应该是我刚才说的调用的别人封装好的异步函数。比如下图这个是dapper(一个轻量级ORM)对于执行一些数据库query的异步方法,为了调用他们可能会发生上述情况。

那怎么解决这个无限的循环呢?因为大家都知道一直往上肯定是到main函数了,main函数是不能被标记为async的,总得有一个async函数被包含在没有async标记的函数里。当然这里有一种情况就是最底层的async函数层层异步到最上端,作为API被暴露给外界,这也就是我们调用的异步API同理,适用于REST API的那种项目。那么如果不是从顶层async到底层,像一个UI函数,怎么调用一个异步函数呢?

有两种方法:

public static void main()
{
//some code
  myFuncAsync1();
} public async Task myFuncAsync1()
{ 
  //some code
  string s= await myFuncAsync2();
  someLogic(s);
}
public async Task<string> myFuncAsync2()
{
//some code
  int num = await myFuncAsync3();
  string s = someLogic(num);
  return s;
}

第一个是封装到一个没有返回值的异步函数里头, 比如我们的逻辑从底层的myFuncAsyncN一直返回值到myFuncAsync1,在myFuncAsync1中,拿到myFuncAsync2的数据并且完成最后所有操作,不需要再返回任何值进行进一步的操作,那么myFuncAsync1虽然被标记为async函数,但是在main函数调用他的时候,因为没有返回值,所以不需要用await关键字。事实上async/await的循环链都可以止步于一个没有返回值的async函数。

public String DownloadString(String url)
{
  var request = HttpClient.GetAsync(url).Result;
  var download = request.Content.ReadAsStringAsync().Result;
  return download;
}

第二个是利用Task.Result来直接获取结果(会阻塞主进程,慎用!)。如上段代码,本来的async函数,需要用await修饰来异步获取结果,然后再继续执行,现在用result直接等到当前进程把这个异步函数运行完并且拿到结果,result关键字其实是执行完这个task并且拿到结果,否则针对task返回型我们只能await修饰来等待结果。这样做的话避免了await,母函数也就不用async修饰,中断了async/await链。但是用result关键字,其实是阻塞的主进程,然后在一个新进程上运行异步task,得到结果之后,返回给主进程,主进程再继续往下走,注意主进程在新进程去获取结果的这段时间,是不能做别的事的,只能干等在这里,所以和主进程自己去做这个task然后得到结果并没有卵区别,可以说就是一个多浪费了一个进程的同步。而await修饰的话,主进程到了await这里就可以被释放干别的去,如果主进程是UI进程的话,UI就不会卡顿。事实上await保存了上下文,封装了后半部分代码,等到await等来了结果,他会安排一个新的进程,给他上下文让他继续运行,所以await才真正做到了充分利用进程。

public String DownloadString(String url)
{
  var runInBackground = Task.Run(()=>HttpClient.GetAsync(url)); //假设GetAsync耗时5秒
  var runInBackground2 = Task.Run(()=>sql.QueryAsync(q)) //也耗时5秒
  //上边两个task并行
  var request = runInBackground.Result; //5s之后拿到结果,主进程阻塞了5秒
  var db = runInBackground.Result; //同时拿到结果,无需等待
  //两个task共耗时5秒
  ...
}

那有人就问了,既然这样,result关键字好像有百害而无一利,为什么还要用,其实我们可以配合Task.Run()使用,Task.Run()会在后台开一个新进程1去运行GetAsync这个task需要5秒,同时因为没有使用result关键字,主进程并没有马上需要用到result,会继续往下执行,假设下边有另外一个Task.Run()开启新进程2去执行另外一个task,也耗时5秒,然后到获取第一个result时候,主进程阻塞5秒,新进程1返回结果,在新进程1获取结果这5秒,新进程2也同时拿到了结果,所以第二个result主进程不会阻塞,直接拿到了结果,这也是完成了两个并行的异步任务。和await相比主进程只是在被阻塞的5秒内不能做别的事。如果是UI进程的话,相对于上段代码的阻塞10秒,这里就只阻塞5秒。






小白终于弄懂了:c#从async/await到Task再到Thread的更多相关文章

  1. 我终于弄懂了Python的装饰器(一)

    此系列文档: 1. 我终于弄懂了Python的装饰器(一) 2. 我终于弄懂了Python的装饰器(二) 3. 我终于弄懂了Python的装饰器(三) 4. 我终于弄懂了Python的装饰器(四) 一 ...

  2. 我终于弄懂了Python的装饰器(二)

    此系列文档: 1. 我终于弄懂了Python的装饰器(一) 2. 我终于弄懂了Python的装饰器(二) 3. 我终于弄懂了Python的装饰器(三) 4. 我终于弄懂了Python的装饰器(四) 二 ...

  3. 我终于弄懂了Python的装饰器(四)

    此系列文档: 1. 我终于弄懂了Python的装饰器(一) 2. 我终于弄懂了Python的装饰器(二) 3. 我终于弄懂了Python的装饰器(三) 4. 我终于弄懂了Python的装饰器(四) 四 ...

  4. async,await与task.wait()或task.Result的区别

    你是否曾经与我一样不理解async,await与task.wait()或者task.Result的区别? 接下来,一个Demo让你看出他们之间的区别. static void Main(string[ ...

  5. 初步学习async/await,Task.GetAwaiter,Task.Result

    网上关于async/await的知识有很多,看了很多但不如自己实践一遍来得快,所以这里记录下我的理解和大家学习下. 首先以最简单的同步方法来开始如下 private static void Test( ...

  6. [转]Hibernate与Jpa的关系,终于弄懂

    原文地址:http://blog.sina.com.cn/s/blog_5f1619e80100yoxz.html 我知道Jpa是一种规范,而Hibernate是它的一种实现.除了Hibernate, ...

  7. Hibernate与Jpa的关系,终于弄懂

    我知道Jpa是一种规范,而Hibernate是它的一种实现.除了Hibernate,还有EclipseLink(曾经的toplink),OpenJPA等可供选择,所以使用Jpa的一个好处是,可以更换实 ...

  8. 移动设备分辨率(终于弄懂了为什么移动端设计稿总是640px和750px)

    在我开始写移动端页面至今,一直有2个疑问困扰着我,我只知道结果但不知道为什么 问题1:为什么设计师给的设计稿总是640px或750px(现在一般以Phone6为基准,给的750px) 问题2:为什么我 ...

  9. 学习Python一年,这次终于弄懂了浅拷贝和深拷贝

    官方文档:copy主题 源代码: Lib/copy.py 话说,网上已经有很多关于Python浅拷贝和深拷贝的文章了,不过好多文章看起来还是决定似懂非懂,所以决定用自己的理解来写出这样一篇文章. 当别 ...

随机推荐

  1. python最基本的数据掌握

    python初学者可能会对list数据类型和int或者是字符串数据类型比较迷茫  list是引用,是指向的一个内存地址, 变量不是引用的 啥也不说上解释: a = 1 b = a a = 2 prin ...

  2. Linux之VMWare下Centos7的三种网络配置过程

    Linux之VMWare下Centos7的三种网络配置过程 环境: 虚拟软件:VMWare 14.0 客户机:windows 10 虚拟机:centos 7 VMware三种网络连接方式 Bridge ...

  3. Nginx反向代理之动静分离

    我们已经知道了什么是正向代理与反向代理,这次我们就讲一下Nginx的动静分离的案例,其实质运用的就是反向代理,专门用一台服务器代理服务器上的图片资源. 想使用代理必然要配置代理,配置反向代理,必须要用 ...

  4. 构建之法——homework1:问题思考

    1.我看了第一章概论,1.2.4 软件工程的目标——创造“足够好”的软件,其中提到了什么是好的软件?  软件工程的一个要素就是把软件的Bug都消灭掉的过程. 提问:我们知道Bug是不可能完全消灭掉的, ...

  5. Spring 梳理-跨重定向请求传递数据-Flash

    Spring MVC Flash Attribute 的讲解与使用示例 1. Spring MVC 3.1版本加了一个很有用的特性,Flash属性,它能解决一个长久以来缺少解决的问题,一个POST/R ...

  6. Mybatis面试题吐血总结

    高强度训练第二十天总结:Mybatis面试题 什么是Mybatis? Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时 只需要关注 SQL 语句本身,不需要花费精力 ...

  7. Python3 学习笔记之 类型/运算符

    类型/运算符: 类型: 整数 字符串 浮点数 布尔类型 类型转换: 检查类型: 算术操作符: 逻辑操作符: 优先级:

  8. ELK 学习笔记之 elasticsearch启动时Warning解决办法

    elasticsearch启动时Warning解决办法: 转载:http://www.dajiangtai.com/community/18136.do?origin=csdn-geek&dt ...

  9. MongoDB 学习笔记之 DBRef

    DBRef: MongoDB建模有两种方式,一种是内嵌(Embed),另一种是连接(Link).内嵌比较好理解,就是字段内容是个数组,数组内再包含文档,而我们今天介绍的是另一种,称为链接DBRef.由 ...

  10. 教你制作挂件头像 | 小程序七十二变之 canvas 绘制国旗头像

    昨天朋友圈被「请给我一面国旗@微信官方」刷屏,虽然知道是假的,但是从另一个角度来看,弄清楚如何实现更有趣. 1.canvas 这就不得不提到小程序中的 API canvas,H5 中也是有 canva ...