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代码对域名ssl证书进行监控

    根据公司要求,要求用zabbix对域名的ssl证书进行到期监控 直接上代码 #!/usr/bin/env python3 from urllib3.contrib import pyopenssl f ...

  2. 将CDH中的hive和hbase相互整合使用

    一..hbase与hive的兼容版本: hive0.90与hbase0.92是兼容的,早期的hive版本与hbase0.89/0.90兼容,不需要自己编译. hive1.x与hbase0.98.x或则 ...

  3. JVM 调优 - JPS

    Java命令学习系列(一)——Jps 2015-04-16 分类:Java 阅读(23993) 评论(7) 阿里大牛珍藏架构资料,点击链接免费获取 jps位于jdk的bin目录下,其作用是显示当前系统 ...

  4. 冒泡排序--JavaScript描述

    相信凡是编程入门的都接触过冒泡排序算法,排序算法在编程中经常用到. 1. code /** * 冒泡排序 * 1.比较的轮数等于总数 - 1 * 2.比较次数等于要比较的个数 - 1 * --比较从第 ...

  5. Scala 学习笔记之集合(3)

    建立一个Java类,为了演示Java集合类型向Scala集合的转换: import java.util.ArrayList; import java.util.List; public class S ...

  6. php数字函数

    is_numeric() 检查变量是否包含一个合法数字 round()  取整数,四舍五入 round(数字, 小数位) ceil()  向上取整 floor() 向下取整 range()  生成范围 ...

  7. Java基础学习(八) - 多线程

    理解线程 进程是指一个内存中运行的应用程序,系统运行一个程序即是一个进程从创建,运行,结束的过程. 线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程. 多线程的特点是并发 ...

  8. 控制器向视图传参ModelAndView、Model和Map

    ModelAndView类 ModelAndView在spring-webmvc-4.3.18.RELEASE.jar包下,当然其他版本也有,所在包如下 对于那些返回String等类型的处理方法,sp ...

  9. MOV与LEA

    MOV 格式:MOV dest, src 作用:赋值,且不改变标记位的值 特点:可以从寄存器到寄存器.从立即数到寄存器.从存储单元到寄存器.从立即数到储存单元.从寄存器到存储单元.从寄存器或存储单元到 ...

  10. selenium3与Python3实战 web自动化测试框架 ☝☝☝

    selenium3与Python3实战 web自动化测试框架 selenium3与Python3实战 web自动化测试框架 学习 教程 一.环境搭建 1.selenium环境搭建 Client: py ...