我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C#异步编程之async/await怎么使用

今天小编给大家分享一下C#异步编程之async/await怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。概述异步这个
2023-07-05

C#怎么使用async和await实现异步编程

这篇文章主要介绍“C#怎么使用async和await实现异步编程”,在日常操作中,相信很多人在C#怎么使用async和await实现异步编程问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C#怎么使用async
2023-07-02

JS中的async与await异步编程及await使用陷阱

这篇文章主要介绍了async与await异步编程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2023-03-24

使用Node.js的async和await进行异步编程

使用异步编程可以提高Node.js应用程序的性能,而async和await是Node.js中实现异步编程的一种简单且易于使用的方式,可以帮助开发者避免回调地狱和处理异步操作时的错误
2023-05-18

JS中的async与await异步编程及await使用陷阱源码分析

这篇“JS中的async与await异步编程及await使用陷阱源码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“JS中
2023-07-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录