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. uboot学习之uboot.bin的运行流程

    上篇博客:http://www.cnblogs.com/yeqluofwupheng/p/7347925.html 讲到uboot-spl的工作流程,接下来简述一下uboot.bin的工作流程,这对应 ...

  2. (转)在阿里云 CentOS 服务器(ECS)上搭建 nginx + mysql + php-fpm 环境

    阿里云的云服务器(ECS)可以选择多种操作系统,打算用它运行 Drupal或者 WordPress ,你最好选择 Linux 系统,这篇文章的演示是基于阿里云的 CentOS 操作系统的服务器.我们在 ...

  3. 08.Django基础六之ORM中的锁和事务

    一 锁 行级锁 select_for_update(nowait=False, skip_locked=False) #注意必须用在事务里面,至于如何开启事务,我们看下面的事务一节. 返回一个锁住行直 ...

  4. Mybatis逆向工程过程中出现targetRuntime in context mybatisGenerator is invalid

    最开始设置的Mybatis,但是逆向工程准备就绪后出现问题 报错为targetRuntime in context mybatisGenerator is invalid 后来修改为Mybatis3能 ...

  5. 性能测试之 Gatling

    在应用程序上线之前,有多少人做过性能测试? 估计大部分开发者更多地关注功能测试,并且会提供一些单元测试和集成测试的用例.然而,有时候性能漏洞导致的影响比未发现的业务漏洞更严重,因为性能漏洞影响的是整个 ...

  6. Apache和Tomcat 配置负载均衡(mod-proxy方式)-无session共享、无粘性session

    转:https://blog.csdn.net/wangjunjun2008/article/details/38268483 mod-proxy方式实现负载均衡是利用了Apache 2.x版本自带的 ...

  7. freemarker常用属性

    1.th:action 定义后台控制器的路径,类似<form>标签的action属性. 示例如下. <form id="login" th:action=&quo ...

  8. 虚拟机中设置 CentOS 静态 IP

    作为开发人员在工作和学习中通过虚拟机使用 Linux 的情况肯定会非常多,但是 Linux 自带的终端使用体验较差,所以绝大多数使用者都会在宿主机上使用第三方 SSH工具(例如 Xshell)进行连接 ...

  9. CentOS7下mongodb忘记密码后重置密码

    新装mongodb后,结果一段时间没有用,密码给忘记了,只能重置密码了. 步骤如下: 1.找到mongodb的配置文件 通过ps -ef|grep mongod找到mongodb的配置文件mongod ...

  10. opencv之为图像添加边界

    我们经常会有对图像边缘做扩展的需求.比如 希望卷积后得到的矩阵大小不变 希望改变图像大小,但是不改变宽高比 opencv实现 opencv中使用copyMakeBorder()来完成这一功能 api ...