天天写,不一定就明白。

又及,前两天看了一个关于同步方法中调用异步方法的文章,里面有些概念不太正确,所以整理了这个文章。

一、同步和异步。

先说同步。

同步概念大家都很熟悉。在异步概念出来之前,我们的代码都是按同步的方式写的。简单来说,就是程序严格按照代码的逻辑次序,一行一行执行。

看一段代码:

public static void Main(string[] args)
{
    Console.WriteLine("Syc proccess - start");     Console.WriteLine("Syc proccess - enter Func1");
    func1();
    Console.WriteLine("Syc proccess - out Func1");     Console.WriteLine("Syc proccess - enter Func2");
    func2();
    Console.WriteLine("Syc proccess - out Func2");     Console.WriteLine("Syc proccess - enter Func3");
    func3();
    Console.WriteLine("Syc proccess - out Func3");     Console.WriteLine("Syc proccess - done");
} private static void func1()
{
    Console.WriteLine("Func1 proccess - start");
    Thread.Sleep(1000);
    Console.WriteLine("Func1 proccess - end");
} private static void func2()
{
    Console.WriteLine("Func2 proccess - start");
    Thread.Sleep(3000);
    Console.WriteLine("Func2 proccess - end");
} private static void func3()
{
    Console.WriteLine("Func3 proccess - start");
    Thread.Sleep(5000);
    Console.WriteLine("Func3 proccess - end");
}

这是一段简单的通常意义上的代码,程序按代码的次序同步执行,看结果:

Syc proccess - start
Syc proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Syc proccess - out Func1
Syc proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Syc proccess - out Func2
Syc proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Syc proccess - out Func3
Syc proccess - done

没有任何意外。

    为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13357981.html

那异步呢?

异步,来自于对同步处理的改良和优化。

应用中,经常会有对于文件或网络、数据库的IO操作。这些操作因为IO软硬件的原因,需要消耗很多时间,但通常情况下CPU计算量并不大。在同步的代码中,这个过程会被阻塞。直白的说法就是这一行代码没执行完成,程序就得等着,等完成后再执行下一行代码,而这个等待的时间中,CPU资源就被浪费了,闲着了,什么也没做。(当然,操作系统会调度CPU干别的,这儿不抬杠。)

异步编程模型和规范因此出现了,通过某种机制,让程序在等着IO的过程中,继续做点别的事,等IO的过程完成了,再回来处理IO的内容。这样CPU也没闲着,在等IO的过程中多做了点事。反映到用户端,就感觉程序更快了,用时更短了。

下面重点说一下异步编程相关的内容。

二、异步编程

C#中,异步编程,一个核心,两个关键字。

一个核心是指TaskTask<T>对象,而两个关键字,就是asyncawait

从各种渠道给出的异步编程,都是下面的方式:

async Task function()
{
  /* your code here */
}

然后调用的方式:

await function();

是这样的吗?嗯,图样图森破~~~

我们来看代码:

static async Task Main(string[] args)
{
    Console.WriteLine("Async proccess - start");     Console.WriteLine("Async proccess - enter Func1");
    await func1();
    Console.WriteLine("Async proccess - out Func1");     Console.WriteLine("Async proccess - enter Func2");
    await func2();
    Console.WriteLine("Async proccess - out Func2");     Console.WriteLine("Async proccess - enter Func3");
    await func3();
    Console.WriteLine("Async proccess - out Func3");     Console.WriteLine("Async proccess - done");     Console.WriteLine("Main proccess - done");
} private static async Task func1()
{
    Console.WriteLine("Func1 proccess - start");
    Thread.Sleep(1000);
    Console.WriteLine("Func1 proccess - end");
} private static async Task func2()
{
    Console.WriteLine("Func2 proccess - start");
    Thread.Sleep(3000);
    Console.WriteLine("Func2 proccess - end");
} private static async Task func3()
{
    Console.WriteLine("Func3 proccess - start");
    Thread.Sleep(5000);
    Console.WriteLine("Func3 proccess - end");
}

跑一下结果:

Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Async proccess - out Func3
Async proccess - done
Main proccess - done

咦?这个好像跟同步代码的执行结果没什么区别啊?

嗯,完全正确。上面这个代码,真的是同步执行的。

这是异步编程的第一个容易错误的理解:asyncawait的配对。

三、async和await的配对

在异步编程的规范中,async修饰的方法,仅仅表示这个方法在内部有可能采用异步的方式执行,CPU在执行这个方法时,会放到一个新的线程中执行。

那这个方法,最终是否采用异步执行,不决定于是否用await方式调用这个方法,而决定于这个方法内部,是否有await方式的调用。

看代码,很容易理解:

private static async Task func1()
{
    Console.WriteLine("Func1 proccess - start");
    Thread.Sleep(1000);
    Console.WriteLine("Func1 proccess - end");
}

这个方法,因为方法内部没有await调用,所以这个方法永远会以同步方式执行,不管你调用这个方法时,有没有await

而下面这个代码:

private static async Task func1()
{
    Console.WriteLine("Func1 proccess - start");
    await Task.Run(() => Thread.Sleep(1000));
    Console.WriteLine("Func1 proccess - end");
}

因为这个方法里有await调用,所以这个方法不管你以什么方式调用,有没有await,都是异步执行的。

看代码:

static async Task Main(string[] args)
{
    Console.WriteLine("Async proccess - start");     Console.WriteLine("Async proccess - enter Func1");
    func1();
    Console.WriteLine("Async proccess - out Func1");     Console.WriteLine("Async proccess - enter Func2");
    func2();
    Console.WriteLine("Async proccess - out Func2");     Console.WriteLine("Async proccess - enter Func3");
    func3();
    Console.WriteLine("Async proccess - out Func3");     Console.WriteLine("Async proccess - done");     Console.WriteLine("Main proccess - done");     Console.ReadKey();
} private static async Task func1()
{
    Console.WriteLine("Func1 proccess - start");
    await Task.Run(() => Thread.Sleep(1000));
    Console.WriteLine("Func1 proccess - end");
} private static async Task func2()
{
    Console.WriteLine("Func2 proccess - start");
    await Task.Run(() => Thread.Sleep(3000));
    Console.WriteLine("Func2 proccess - end");
} private static async Task func3()
{
    Console.WriteLine("Func3 proccess - start");
    await Task.Run(() => Thread.Sleep(5000));
    Console.WriteLine("Func3 proccess - end");
}

输出结果:

Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Async proccess - out Func3
Async proccess - done
Main proccess - done
Func1 proccess - end
Func2 proccess - end
Func3 proccess - end

结果中,在长时间运行Thread.Sleep的时候,跳出去往下执行了,是异步。

又有问题来了:不是说异步调用要用await吗?

我们把await加到调用方法的前边,试一下:

static async Task Main(string[] args)
{
    Console.WriteLine("Async proccess - start");     Console.WriteLine("Async proccess - enter Func1");
    await func1();
    Console.WriteLine("Async proccess - out Func1");     Console.WriteLine("Async proccess - enter Func2");
    await func2();
    Console.WriteLine("Async proccess - out Func2");     Console.WriteLine("Async proccess - enter Func3");
    await func3();
    Console.WriteLine("Async proccess - out Func3");     Console.WriteLine("Async proccess - done");     Console.WriteLine("Main proccess - done");     Console.ReadKey();
}

跑一下结果:

Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Async proccess - out Func3
Async proccess - done
Main proccess - done

嗯?怎么又像是同步了?

对,这是第二个容易错误的理解:await是什么意思?

四、await是什么意思

提到await,就得先说说Wait

字面意思,Wait就是等待。

前边说了,异步有一个核心,是Task。而Task有一个方法,就是Wait,写法是Task.Wait()。所以,很多人把这个Waitawait混为一谈,这是错的

这个问题来自于Task。C#里,Task不是专为异步准备的,它表达的是一个线程,是工作在线程池里的一个线程。异步是线程的一种应用,多线程也是线程的一种应用。Wait,以及StatusIsCanceledIsCompletedIsFaulted等等,是给多线程准备的方法,跟异步没有半毛钱关系。当然你非要在异步中使用多线程的Wait或其它,从代码编译层面不会出错,但程序会。

尤其,Task.Wait()是一个同步方法,用于多线程中阻塞等待。

在那个「同步方法中调用异步方法」的文章中,用Task.Wait()来实现同步方法中调用异步方法,这个用法本身就是错误的。 异步不是多线程,而且在多线程中,多个Task.Wait()使用也会死锁,也有解决和避免死锁的一整套方式。

再说一遍:Task.Wait()是一个同步方法,用于多线程中阻塞等待,不是实现同步方法中调用异步方法的实现方式。

说回await。字面意思,也好像是等待。是真的吗?

并不是,await不完全是等待的意思。

在异步中,await表达的意思是:当前线程/方法中,await引导的方法出结果前,跳出当前线程/方法,从调用当前线程/方法的位置,去执行其它可能执行的线程/方法,并在引导的方法出结果后,把运行点拉回到当前位置继续执行;直到遇到下一个await,或线程/方法完成返回,跳回去刚才外部最后执行的位置继续执行。

有点绕,还是看代码:

  static async Task Main(string[] args)
  {
1     Console.WriteLine("Async proccess - start"); 2     Console.WriteLine("Async proccess - enter Func1");
3     func1();
4     Console.WriteLine("Async proccess - out Func1"); 5     Console.WriteLine("Async proccess - done"); 6         Thread.Sleep(2000); 7     Console.WriteLine("Main proccess - done"); 8    Console.ReadKey();
  }   private static async Task func1()
  {
9     Console.WriteLine("Func1 proccess - start");
10    await Task.Run(() => Thread.Sleep(1000));
11    Console.WriteLine("Func1 proccess - end");
  }

这个代码,执行时是这样的:顺序执行1、2、3,进到func1,执行9、10,到10时,有await,所以跳出,执行4、5、6。而6是一个长时等待,在等待的过程中,func1的10运行完成,运行点跳回10,执行11并结束方法,再回到6等待,结束等待后继续执行7、8结束。

我们看一下结果:

Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Async proccess - out Func1
Async proccess - done
Func1 proccess - end
Main proccess - done

映证了这样的次序。

在这个例子中,await在控制异步的执行次序。那为什么要用等待这么个词呢?是因为await确实有等待结果的含义。

这是await的第二层意思。

五、await的第二层意思:等待拿到结果

await确实有等待的含义。等什么?等异步的运行结果。

看代码:

static async Task Main(string[] args)
{
    Console.WriteLine("Async proccess - start");     Console.WriteLine("Async proccess - enter Func1");
    Task<int> f = func1();
    Console.WriteLine("Async proccess - out Func1");     Console.WriteLine("Async proccess - done");     int result = await f;     Console.WriteLine("Main proccess - done");     Console.ReadKey();
} private static async Task<int> func1()
{
    Console.WriteLine("Func1 proccess - start");
    await Task.Run(() => Thread.Sleep(1000));
    Console.WriteLine("Func1 proccess - end");     return 5;
}

比较一下这段代码和上一节的代码,很容易搞清楚执行过程。

这个代码,完成了这样一个需求:我们需要使用func1方法的返回值。我们可以提前去执行这个方法,而不急于拿到方法的返回值,直到我们需要使用时,再用await去获取到这个返回值去使用。

这才是异步对于我们真正的用处。对于一些耗时的IO或类似的操作,我们可以提前调用,让程序可以利用执行过程中的空闲时间来完成这个操作。等到我们需要这个操作的结果用于后续的执行时,我们await这个结果。这时候,如果await的方法已经执行完成,那我们可以马上得到结果;如果没有完成,则程序将继续执行这个方法直到得到结果。

六、同步方法中调用异步

正确的方法只有一个:

func1().GetAwaiter().GetResult();

这其实就是await的一个变形。

(全文完)


微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

一文说通C#中的异步编程的更多相关文章

  1. 一文说通C#中的异步编程补遗

    前文写了关于C#中的异步编程.后台有无数人在讨论,很多人把异步和多线程混了. 文章在这儿:一文说通C#中的异步编程 所以,本文从体系的角度,再写一下这个异步编程.   一.C#中的异步编程演变 1. ...

  2. 一文说通C#中的异步迭代器

    今天来写写C#中的异步迭代器 - 机制.概念和一些好用的特性   迭代器的概念 迭代器的概念在C#中出现的比较早,很多人可能已经比较熟悉了. 通常迭代器会用在一些特定的场景中. 举个例子:有一个for ...

  3. .Net中的异步编程总结

    一直以来很想梳理下我在开发过程中使用异步编程的心得和体会,但是由于我是APM异步编程模式的死忠,当TAP模式和TPL模式出现的时候我并未真正的去接纳这两种模式,所以导致我一直没有花太多心思去整理这两部 ...

  4. C#中的异步编程Async 和 Await

    谈到C#中的异步编程,离不开Async和Await关键字 谈到异步编程,首先我们就要明白到底什么是异步编程. 平时我们的编程一般都是同步编程,所谓同步编程的意思,和我们平时说的同时做几件事情完全不同. ...

  5. .NET中的异步编程——常见的错误和最佳实践

    在这篇文章中,我们将通过使用异步编程的一些最常见的错误来给你们一些参考. 背景 在之前的文章<.NET中的异步编程——动机和单元测试>中,我们开始分析.NET世界中的异步编程.在那篇文章中 ...

  6. javaScript中的异步编程模式

    1.事件模型 let button = document.getElementById("my-btn"); button.onclick = function(event) { ...

  7. Netty 中的异步编程 Future 和 Promise

    Netty 中大量 I/O 操作都是异步执行,本篇博文来聊聊 Netty 中的异步编程. Java Future 提供的异步模型 JDK 5 引入了 Future 模式.Future 接口是 Java ...

  8. promise 的基本概念 和如何解决js中的异步编程问题 对 promis 的 then all ctch 的分析 和 await async 的理解

    * promise承诺 * 解决js中异步编程的问题 * * 异步-同步 * 阻塞-无阻塞 * * 同步和异步的区别? 异步;同步 指的是被请求者 解析:被请求者(该事情的处理者)在处理完事情的时候的 ...

  9. .NET中的异步编程

    开篇 异步编程是程序设计的重点也是难点,还记得在刚开始接触.net的时候,看的是一本c#的Winform实例教程,上面大部分都是教我们如何使用Winform的控件以及操作数据库的实例,那时候做的基本都 ...

随机推荐

  1. Typora及Markdown的介绍及使用

    Typora及Markdown的介绍及使用 Typora是一款免费的Markdown编辑器,Typora不像其他Markdown编辑器一样使用一边代码一边预览的方式,而是写完代码之后直接出效果,所见即 ...

  2. eclipse导入git项目

    复制项目的git路径 Eclipse打开 Git Repostitories 视图 弹出show view窗口 选择ok ,进入git repositories 视图窗口 我这里已经导入从我的git仓 ...

  3. 【Xamarin.Forms 1】App的创建与运行

    引言 本篇文章将从介绍Xamarin.Forms创建开始. 开发环境 Visual Studio 2019 16.6.2 Xamarin.Forms 4.6.0.726 Android 5.0 (AP ...

  4. Nginx 从入门到放弃(三)

    今天来学习nginx的日志管理,并通过日志脚本来切割日志并保存. nginx日志管理 在nginx中设置日志格式  http {    log_format main  '$remote_addr - ...

  5. Asp.Net Core Blazor之容器部署

    写在前面 Docker作为开源的应用容器引擎,可以让我们很轻松的构建一个轻量级.易移植的容器,通过Docker方式进行持续交付.测试和部署,都是极为方便的,并且对于我们开发来说,最直观的优点还是解决了 ...

  6. CSS3 target 选择器_:target伪类的使用

    target作为目标伪类选择器,是css3中的新特性之一,目前已经支持所有主流浏览器,除了 IE8 及更早的版本.target伪类的主要是用于匹配文档中uri中某个标志符的目标元素,具体来说,uri中 ...

  7. 逻辑式编程语言极简实现(使用C#) - 4. 代码实现(完结)

    本文是本系列的完结篇.本系列前面的文章: 逻辑式编程语言极简实现(使用C#) - 1. 逻辑式编程语言介绍 逻辑式编程语言极简实现(使用C#) - 2. 一道逻辑题:谁是凶手 逻辑式编程语言极简实现( ...

  8. [Noip2016]蚯蚓 (单调队列)

    题干 本题中,我们将用符号[c]表示对c向下取整,例如:[3.0」= [3.1」=[3.9」=3.蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓.蛐蛐国 ...

  9. [TZOJ] 平台训练-V1

    日常训练 训练网址:http://www.tzcoder.cn/ 1001: 整数求和 描述求两个整数之和.输入输入数据只包括两个整数A和B.输出两个整数的和.样例输入1 2样例输出3题目来源TZOJ ...

  10. 最新MySQL入门篇

    一.SQL简介 ​ SQL:结构化查询语言(Structured Query Language),是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询.更新和管理关系数据库系 ...