C#异步方法关键字async和await

Task原理

1、async关键字和await是配套使用的异步方法语法糖,配合Task类可以使多线程变得有序,也可以自己实现一套协程功能,具体可参考:项目地址

2、这里首先介绍一下异步执行原理,后半部分介绍其基本使用。
async await语法糖本质是一个状态机,一般由三种类组成:Builder类,Await类和Task类。这里使用ILSpy反编译工具查看语法糖的本质(C#版本需设置为5.0以前的版本)

一个最简单异步Task用法,async修饰Task

        public async Task TaskTest()
        {
            await Task.Run(() => { });
        }

ILSpy反编译后看到的(C#3.0):

    [CompilerGenerated]
    private sealed class <TaskTest>d__1 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder <>t__builder;

        public ClassA <>4__this;

        private TaskAwaiter <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            try
            {
                TaskAwaiter awaiter;
                if (num != 0)
                {
                    awaiter = Task.Run(delegate
                    {
                    }).GetAwaiter();
                    if (!awaiter.IsCompleted) //await修饰的状态未完成?把控制器交由该类
                    {
                        num = (<>1__state = 0);
                        <>u__1 = awaiter;
                        <TaskTest>d__1 stateMachine = this;
                        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                        return;
                    }
                }
                else
                {
                    awaiter = <>u__1;
                    <>u__1 = default(TaskAwaiter);
                    num = (<>1__state = -1);
                }
                awaiter.GetResult();
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult();
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

    [AsyncStateMachine(typeof(<TaskTest>d__1))]
    [DebuggerStepThrough]
    public Task TaskTest()
    {
        <TaskTest>d__1 stateMachine = new <TaskTest>d__1();
        stateMachine.<>4__this = this;
        stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create(); //创那就状态机
        stateMachine.<>1__state = -1; //初始状态-1
        AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
        <>t__builder.Start(ref stateMachine); //启动状态机
        return stateMachine.<>t__builder.Task; //返回值
    }

可以看到这里使用AsyncTaskMethodBuilder来开启状态机,以async修饰的类指定的Builder(可以使用AsyncMethodBuilder特性来指定Builder,后续会详细说明)创建并开启状态机,并在最后返回值。Task.Run返回的是Task对象(如果async修饰的是void则不需要返回值)

使用await修饰的类(这里是Task)会调用GetAwaiter方法(即被await修饰的类需要有GetAwaiter方法)。C#会为该方法生成一个迭代类,该类会生成一个MoveNext方法(以await关键字为分界线,每个await作为一个状态)。

上述总结
使用async修饰的类指定的Builder类(使用AsyncMethodBuilder特性指定)开启状态机A并返回值(Builder.Task,无论状态是否完成),
await修饰的类会在MoveNext方法执行GetAwaiter方法获取Awaiter(状态),执行该状态时,如果该状态未完成,则将状态机的控制权交由该状态,该状态完成时继续执行状态机。

使用状态机A迭代调用这些Awaiter,每个Awaiter会判断其IsCompleted属性是否为ture,如为false则调用AwaitUnsafeOnCompleted方法中断调用,我们应当在这个Awaiter完成后继续调用MoveNext方法执行状态机。
总而言之,如果当前状态已完成,则继续调用下一个状态直到完成,如果当前状态未完成,则将控制权(即完成后继续执行的方法)移交给这个未完成的状态,当这个状态确认自己完成后再调用继续执行的方法即可。

自定义STask类,使其能被async和await语法支持

接下来我们自己自定义一个类,使其可以被async await语法糖支持。
还是由三种类组成:Builder,Awaiter和Task。
Builder类会调用Create、Start、AwaitUnsafeOnCompleted等一系列方法,自定义Builder需要定义它们。
Awaiter类需要继承INotifyCompletion接口,在MoveNext中判断了IsCompleted属性以及在最后调用了GetResult方法。
Task类会调用GetAwaiter方法获取Awaiter。

    public struct AsyncSTaskMethodBuilder
    {   //第一步,创建Builder
        public static AsyncSTaskMethodBuilder Create()
        {
            return default(AsyncSTaskMethodBuilder);
        }

        private STask task;

        public STask Task { get => task; } //最后返回的值(无论状态是否完成)

        //第二步:开启状态机
        public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 
        {
            stateMachine.MoveNext();
        }

        //执行完成时调用
        public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) 
            where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine
        {
            awaiter.OnCompleted(stateMachine.MoveNext);
        }

       //该状态尚未执行完毕,获得控制权(MoveNext方法)
        public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
            where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
        {
            awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
        }

        public void SetException(Exception exception)
        {

        }

        public void SetResult()
        {

        }

        public void SetStateMachine(IAsyncStateMachine stateMachine)
        {

        }

    }
    [AsyncMethodBuilder(typeof(AsyncSTaskMethodBuilder))]//指定Builder类,async修饰所必须
    public class STask
    {
        public IAwaiter GetAwaiter()//await修饰所必须
        {
            return new Awaiter(this);
        }

        //间接继承INotifyCompletion
        public struct Awaiter : ICriticalNotifyCompletion
        {
            private STask coroutine;
            internal Awaiter(STask coroutine)
            {
                this.coroutine = coroutine;
                IsCompleted = false;
            }

            public bool IsCompleted { get; }//判断状态是否完成

             //继续执行下一个状态
            public void OnCompleted(Action continuation)
            {
                Console.WriteLine("OnCompleted");
               UnsafeOnCompleted(continuation);
            }

             //等待本状态的事务执行完毕后,调用continuation方法继续执行状态机(保存这个委托)
            public void UnsafeOnCompleted(Action continuation)
            {
                Console.WriteLine("UnsafeOnCompleted");
                if (IsCompleted == true)
                {
                    continuation?.Invoke();
                    return;
                }
            }

            public void GetResult()
            {

            }
        }
    }

可以看到,下方代码中,STask 类可以被async await语法糖支持

        public async STask TestTask()
        {
            await new STask();
        }

async和await搭配Task类的用法:

延时执行不使用语法糖

        public static void PreWaitExecute(Action action,int waitTime)
        {
            Console.WriteLine("begin execute task");
            Task task = Task.Run(() =>  //创建任务
            {
                Task delay = Task.Delay(waitTime);    //创建子任务,异步等待(不阻塞线程,等待延时任务完成)
                delay.Wait();
                action.Invoke();    //开始执行方法
            });

            task.Wait();    //等待任务执行完毕
            Console.WriteLine("task finish");
        }

返回值可写可不写,如需返回值,只需return T即可,可以使用Task.Result获取return的返回值。

延时执行使用语法糖

        public static async void WaitExecute(Action action,int waitTime)
        {
            Console.WriteLine("begin execute task");

            Task task=Task.Run(async ()=>
            {
                Task delay=Task.Delay(waitTime);    //异步等待(创建延时任务,等待延时任务完成)
                await delay;
                action.Invoke();    //任务开始执行
            });

            await task; //等待任务执行完毕

            Console.WriteLine("task finish");
        }

异步获取控制台输入的一行字符

        public static async  Task<string> ReadInput()
        {
            string inputStr = null;
            await Task.Run(() =>  //异步等待输入字符
            {
                inputStr = Console.ReadLine();
            });

            return inputStr;  //返回输入的字符
        }

        static void Main(string[] args)
        {
            Task<string> readStrTask = ReadInput();
            readStrTask.ContinueWith((Task<string> task) =>
            {
                Console.WriteLine("str:"+task.Result);  //异步输入字符任务完成后打印字符
            });

            while (true)  //保证主线程不会关闭
            {
                Thread.Sleep(100);  
            }
        }

异步读取执行SQL,读取SQL数据,多返回值

        protected async Task<(DbDataReader, DbCommand)> SelectSQLAsync<C>(string sql, params DbParameter[] sqlParams) where C : DbCommand
        {
            DbCommand cmd = Activator.CreateInstance<C>();
            cmd.Connection = dbConn;
            cmd.CommandText = sql;

            for (int i = 0; i < sqlParams.Length; i++)
            {
                cmd.Parameters.Add(sqlParams[i]);
            }

            DbDataReader reader = await cmd.ExecuteReaderAsync();
            return (reader, cmd);
        }

        //测试使用
       (DbDataReader reader, DbCommand cmd) = await SelectSQLAsync<MySqlCommand>(sql);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容

  • 拦截器和过滤器的区别 过滤器和拦截器的区别: ①拦截器是基于java的反射机制的,而过滤器是基于函数回调。②拦截器...
    积极的小太阳阅读 200评论 0 0
  • 原型链的理解 看一个实例 原型和原型链首先要知道几个概念:在js里,继承机制是原型继承。继承的起点是 对象的原型(...
    __bomb__阅读 138评论 0 0
  • 弄懂js异步 讲异步之前,我们必须掌握一个基础知识-event-loop。 我们知道JavaScript的一大特点...
    DCbryant阅读 2,710评论 0 5
  • 1、 [1,2,3].map(parseInt)输出结果? 答案:1,NaN,NaN 解析: ar...
    像鱼_真好听阅读 570评论 0 1
  • Dart类库有非常多的返回Future或者Stream对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗...
    seventhboy阅读 1,202评论 0 2