C#异步编程由浅入深(二)之Async/Await的使用

  考虑到直接讲实现一个类Task库思维有点跳跃,所以本节主要讲解Async/Await的本质作用(解决了什么问题),以及Async/Await的工作原理。实现一个类Task的库则放在后面讲。首先回顾一下上篇博客的场景。

 class Program
    {

        public static string GetMessage()
        {
            return Console.ReadLine();
        }
        public static  string TranslateMessage(string msg)
            return msg;
        public static  void DispatherMessage(string msg)
            switch (msg)
            {
                case "MOUSE_MOVE":
                    {
                        OnMOUSE_MOVE(msg);
                        break;
                    }
                case "MOUSE_DOWN":
                        OnMouse_DOWN(msg);
                default:
                    break;
            }
        public static void OnMOUSE_MOVE(string msg)
            Console.WriteLine("开始绘制鼠标形状");
        public static int Http()
            Thread.Sleep(1000);//模拟网络IO延时
            return 1;
        public static void HttpAsync(Action<int> action,Action error)
            //这里我们用另一个线程来实现异步IO,由于Http方法内部是通过Sleep来模拟网络IO延时的,这里也只能通过另一个线程来实现异步IO
            //但记住,多线程是实现异步IO的一个手段而已,它不是必须的,后面会讲到如何通过一个线程来实现异步IO。
            Thread thread = new Thread(() =>
                try
                {
                    int res = Http();
                    action(res);
                }
                catch
                    error();

            });
            thread.Start();
        public static Task<int> HttpAsync()
            return Task.Run(() =>
                return Http();
        public static void OnMouse_DOWN(string msg)
            HttpAsync()
                .ContinueWith(t =>
                    if(t.Status == TaskStatus.Faulted)
                    }else if(t.Status == TaskStatus.RanToCompletion)
                        Console.WriteLine(1);
                        //做一些工作
                })
                    if (t.Status == TaskStatus.Faulted)
                    else if (t.Status == TaskStatus.RanToCompletion)
                        Console.WriteLine(2);
                        Console.WriteLine(3);
                });
        static void Main(string[] args)
            while (true)
                string msg = GetMessage();
                if (msg == "quit") return;
                string m = TranslateMessage(msg);
                DispatherMessage(m);
    }

  在OnMouse_DOWN这个处理函数中,我们使用Task的ContinueWith函数进行链式操作,解决了回调地狱问题,但是总感觉有点那么不爽,我们假想有个关键字await它能实现以下作用:首先await必须是Task类型,必须是Task类型的(其实不是必要条件,后面会讲到)原因是保证必须有ContinueWith这个函数,如果Task没有返回值,则把await后面的代码放到Task中的ContinueWith函数体内,如果有返回值,则把Await后的结果转化为访问Task.Result属性,文字说的可能不明白,看下示例代码

//无返回值转换前
public async void Example()
{
    Task t = Task.Run(() =>
    {
        Thread.Sleep(1000);
    });
    await t;
    //做一些工作
}
//无返回值转换后
public void Example()
    t.ContinueWith(task =>
        //做一些工作

//有返回值转换前
    Task<int> t = Task.Run<int>(() =>
        return 1;
    int res = await t;
    //使用res做一些工作
//有返回值转换后
        //使用task.Result做一些工作

  看起来不错,但至少有以下问题,如下:

  • 该种转换方法不能很好的转换Try/Catch结构
  • 在循环结构中使用await不好转换
  • 该实现与Task类型紧密联系

  一二点是我自己认为的,但第三点是可以从扩展async/await这点被证明的。但无论怎样,async/await只是对方法按照一定的规则进行了变换而已,它并没有什么特别之处,具体来讲,就是把Await后面要执行的代码放到一个类似ContinueWith的函数中,在C#中,它是以状态机的形式表现的,每个状态都对应一部分代码,状态机有一个MoveNext()方法,MoveNext()根据不同的状态执行不同的代码,然后每个状态部分对应的代码都会设置下一个状态字段,然后把自身的MoveNext()方法放到类似ContinueWith()的函数中去执行,整个状态机由回调函数推动。我们尝试手动转换以下async/await方法。

public static Task WorkAsync()
{
    return Task.Run(() =>
    {
        Thread.Sleep(1000);
        Console.WriteLine("Done!");
    });
}
public static async void Test()
{
    Console.WriteLine("步骤1");
    await WorkAsync();
    Console.WriteLine("步骤2");
    await WorkAsync();
    Console.WriteLine("步骤3");
}

  手动写一个简单的状态机类

public class TestAsyncStateMachine
    {
        public int _state = 0;
        public void Start() => MoveNext();
        public void MoveNext()
        {
            switch(_state)
            {
                case 0:
                    {
                        goto Step0;
                    }
                case 1:
                        goto Step1;
                default:
                        Console.WriteLine("步骤3");
                        return;
            }

        Step0:
                Console.WriteLine("步骤1");
                _state = 1;
                WorkAsync().ContinueWith(t => this.MoveNext());
                return;
        Step1:
                _state = -1;
                Console.WriteLine("步骤2");
        }
    }

  而Test()方法则变成了这样

public static void Test()
{
    new TestAsyncStateMachine().Start();
}

  注意Test()方法返回的是void,这意味这调用方将不能await Test()。如果返回Task,这个状态机类是不能正确处理的,如果要正确处理,那么状态机在Start()启动后,必须返回一个Task,而这个Task在整个状态机流转完毕后要变成完成状态,以便调用方在该Task上调用的ContinueWith得以继续执行,而就Task这个类而言,它是没有提供这种方法(内部有,但没有对外暴露)来主动控制Task的状态的,这个与JS中的Promise不同,JS里面用Reslove函数来主动控制Promise的状态,并导致在该Promise上面的Then链式调用得以继续完成,而在C#里面怎么做呢?既然使用了状态机来实现async/await,那么在转换一个返回Task的函数时肯定会遇到,怎么处理?后面讲。
  首先解决一下与Task类型紧密联系这个问题。
  从状态机中可以看到,主要使用到了Task中的ContinueWith这个函数,它的语义是在任务完成后,执行回调函数,通过回调函数拿到结果,这个编程风格也叫做CPS(Continuation-Passing-Style, 续体传递风格),那么我们能不能把这个函数给抽象出来呢?语言开发者当然想到了,它被抽象成了一个Awaiter因此编译器要求await的类型必须要有GetAwaiter方法,什么样的类型才是Awaiter呢?编译器规定主要实现了如下几个方法的类型就是Awaiter:

  • 必须继承INotifyCompletion接口,并实现其中的OnCompleted(Action continuation)方法
  • 必须包含IsCompleted属性
  • 必须包含GetResult()方法

  第一点好理解,第二点的作用是热路径优化,第三点以后讲。我们再改造一下我们手动写的状态机。

public class TestAsyncStateMachine
{
    public int _state = 0;
    public void Start() => MoveNext();
    public void MoveNext()
    {
        switch(_state)
        {
            case 0:
                {
                    goto Step0;
                }
            case 1:
                    goto Step1;
            default:
                    Console.WriteLine("步骤3");
                    return;
        }
    Step0:
            Console.WriteLine("步骤1");
            _state = 1;
            TaskAwaiter taskAwaiter;
            taskAwaiter = WorkAsync().GetAwaiter();
            if (taskAwaiter.IsCompleted) goto Step1;
            taskAwaiter.OnCompleted(() => this.MoveNext());
            return;
    Step1:
            _state = -1;
            Console.WriteLine("步骤2");
            if (taskAwaiter.IsCompleted) MoveNext();
    }
}

  可以看到去掉了与Task中ContinueWith的耦合关系,并且如果任务已经完成,则可以直接执行下个任务,避免了无用的开销。
  因此我们可以总结一下async/await:

  • async/await只是表示这个方法需要编译器进行特殊处理,并不代表它本身一定是异步的。
  • Task类中的GetAwaiter主要是给编译器用的。

  第一点我们可以用以下例子来证明,有兴趣的朋友可以自己去验证以下,以便加深理解。

//该类型包含GetAwaiter方法,且GetAwaiter()返回的类型包含三个必要条件
public class MyAwaiter : INotifyCompletion
{
    public void OnCompleted(Action continuation)
    {
        continuation();
    }

    public bool IsCompleted { get; }
    public void GetResult()

    public MyAwaiter GetAwaiter() => new MyAwaiter();
}

  一个测试函数,注意必须返回void

public static async void AwaiterTest()
{
    await new MyAwaiter();
    Console.WriteLine("Done");
}

  可以看到这是完全同步进行的。

到此这篇关于C#异步编程由浅入深(二)之Async/Await的作用.的文章就介绍到这了,更多相关C#异步编程Async/Await内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#异步编程async/await用法详解

    异步函数简介 一般指 async 修饰符声明得.可包含await表达式得方法或匿名函数. 声明方式 异步方法的声明语法与其他方法完全一样, 只是需要包含 async 关键字.async可以出现在返回值之前的任何位置, 如下示例: async public static void GetInfoAsync() { //... } public async static void GetInfoAsync() { //... } public static async void GetInfoAsy

  • c# 使用异步编程的方法

    怎么使用异步,就是用委托进行处理,如果委托对象在调用列表中只有一个方法,它就可以异步执行这个方法.委托类有两个方法,叫做BeginInvoke和EndInvoke,它们是用来异步执行使用. 异步有三种模式 等待模式,在发起了异步方法以及做了一些其它处理之后,原始线程就中断,并且等待异步方法完成之后再继续. 轮询模式,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其它的事情. 回调模式,原始线程一直在执行,无需等待或检查发起的线程是否完成.在发起的线程中的引用方法完成之后,发起的线程

  • c# 异步编程基础讲解

    现代应用程序广泛使用文件和网络 I/O.I/O 相关 API 传统上默认是阻塞的,导致用户体验和硬件利用率不佳,此类问题的学习和编码的难度也较大.而今基于 Task 的异步 API 和语言级异步编程模式颠覆了传统模式,使得异步编程非常简单,几乎没有新的概念需要学习. 异步代码有如下特点: 在等待 I/O 请求返回的过程中,通过让出线程来处理更多的服务器请求. 通过在等待 I/O 请求时让出线程进行 UI 交互,并将长期运行的工作过渡到其他 CPU,使用户界面的响应性更强. 许多较新的 .NET

  • c# 异步编程入门

    一.什么算异步?   广义来讲,两个工作流能同时进行就算异步,例如,CPU与外设之间的工作流就是异步的.在面向服务的系统中,各个子系统之间通信一般都是异步的,例如,订单系统与支付系统之间的通信是异步的,又如,在现实生活中,你去馆子吃饭,工作流是这样的,点菜->下单->做你的事->上菜->吃饭,这个也是异步的,具体来讲你和厨师之间是异步的,异步是如此重要,因外它代表者高效率(两者或两者以上的工作可以同时进行),但复杂,同步的世界简单,但效率极极低. 二.在编程中的异步   在编程中,

  • C#异步编程由浅入深(三)之详解Awaiter

      上一篇末尾提到了Awaiter这个类型,上一篇说了,能await的对象,必须包含GetAwaiter()方法,不清楚的朋友可以看上篇文章.那么,Awaiter到底有什么特别之处呢?  首先,从上篇文章我们知道,一个Awaiter必须实现INotifyCompletion接口,这个接口定义如下: namespace System.Runtime.CompilerServices { /// <summary> /// Represents an operation that will sch

  • C#异步编程由浅入深(二)之Async/Await的使用

      考虑到直接讲实现一个类Task库思维有点跳跃,所以本节主要讲解Async/Await的本质作用(解决了什么问题),以及Async/Await的工作原理.实现一个类Task的库则放在后面讲.首先回顾一下上篇博客的场景. class Program { public static string GetMessage() { return Console.ReadLine(); } public static string TranslateMessage(string msg) return m

  • C#异步编程由浅入深(一)

    目录 一.什么算异步? 二.在编程中的异步 三.原始的异步编程模式之回调函数 1.回调函数 一.什么算异步? 广义来讲,两个工作流能同时进行就算异步,例如,CPU与外设之间的工作流就是异步的.在面向服务的系统中,各个子系统之间通信一般都是异步的,例如,订单系统与支付系统之间的通信是异步的,又如,在现实生活中,你去馆子吃饭,工作流是这样的,点菜->下单->做你的事->上菜->吃饭,这个也是异步的,具体来讲你和厨师之间是异步的,异步是如此重要,因外它代表者高效率(两者或两者以上的工作可

  • 详解node Async/Await 更好的异步编程解决方案

    一.异步编程的终极解决方案 前几天写过关于 javascript 异步操作的文章<Javascript Promise 详解>. 最近在学习 Puppeteer的时候又发现另一种异步编程解决方案:Async/Await. 异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种各样的方案,试图解决这个问题. 从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底. 它们都有额外的复杂性,都需要理解抽象的底层运行机制. 在 A

  • .net4.5使用async和await异步编程实例

    关于异步编程的简单理解: 在.NET4.5中新增了异步编程的新特性async和await,使得异步编程更为简单.通过特性可以将这项复杂的工作交给编译器来完成了.之前传统的方式来实现异步编程较为复杂,这样对于程序猿来说处理起来比较困难,调试也没那么方便,后续的维护工作也比较痛苦. Async和Await关键字是C#异步编程的核心.通过使用这两个关键字,你可以使用.NET Framework 或 Windows Runtime的资源创建一个异步方法如同创建一个同步方法一样容易. 接下来通过VS201

  • 详解ES6之async+await 同步/异步方案

    异步编程一直是JavaScript 编程的重大事项.关于异步方案, ES6 先是出现了 基于状态管理的 Promise,然后出现了 Generator 函数 + co 函数,紧接着又出现了 ES7 的 async + await 方案. 本文力求以最简明的方式来疏通 async + await. 异步编程的几个场景 先从一个常见问题开始:一个for 循环中,如何异步的打印迭代顺序? 我们很容易想到用闭包,或者 ES6 规定的 let 块级作用域来回答这个问题. for (let val of [

  • 详解python之异步编程

    目录 一.异步编程概述 二.python的异步框架模型 三.顺序执行多个可重叠的任务 四.异步化同步代码 五.使用多线程克服具体任务的异步限制 总结 一.异步编程概述 异步编程是一种并发编程的模式,其关注点是通过调度不同任务之间的执行和等待时间,通过减少处理器的闲置时间来达到减少整个程序的执行时间:异步编程跟同步编程模型最大的不同就是其任务的切换,当遇到一个需要等待长时间执行的任务的时候,我们可以切换到其他的任务执行: 与多线程和多进程编程模型相比,异步编程只是在同一个线程之内的的任务调度,无法

  • C#异步编程详解

    前言 本节主要介绍异步编程中Task.Async和Await的基础知识. 什么是异步? 异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程. 异步和多线程 相同点:避免调用线程阻塞,从而提高软件的可响应性. 不同点: 异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少 共享变量的数量),减少了死锁的可能.C#5.0 .NET4.5 以后关键字Async和Awai

  • Javascript异步编程之你真的懂Promise吗

    前言 在异步编程中,Promise 扮演了举足轻重的角色,比传统的解决方案(回调函数和事件)更合理和更强大.可能有些小伙伴会有这样的疑问:2020年了,怎么还在谈论Promise?事实上,有些朋友对于这个几乎每天都在打交道的"老朋友",貌似全懂,但稍加深入就可能疑问百出,本文带大家深入理解这个熟悉的陌生人-- Promise. 基本用法 语法 new Promise( function(resolve, reject) {...} /* executor */ ) 构建 Promise

  • JavaScript引擎实现async/await的方法实例

    目录 前言 生成器 VS 协程 async/await async await 小结 总结 前言 我们都知道Promise 能很好地解决回调地狱的问题,但是这种方式充满了 Promise 的 then() 方法,如果处理流程比较复杂的话,那么整段代码将充斥着 then,语义化不明显,代码不能很好地表示执行流程,使用 promise.then 也是相当复杂,虽然整个请求流程已经线性化了,但是代码里面包含了大量的 then 函数,使得代码依然不是太容易阅读.基于这个原因,ES7 引入了 async/

随机推荐