• C# async/await异步编程深入理解


    异步函数简介

    一般指 async 修饰符声明得、可包含await表达式得方法或匿名函数。

    声明方式

    异步方法的声明语法与其他方法完全一样, 只是需要包含 async 关键字。async可以出现在返回值之前的任何位置, 如下示例:

            async public static void GetInfoAsync()
            {
               //...
            }
    
            public async static void GetInfoAsync()
            {
               //...
            }
    
            public static async void GetInfoAsync()
            {
                //...
            }

    异步方法的返回类型

    异步函数的返回类型只能为: void、Task、Task<TResult>。

    Task<TResult>: 代表一个返回值T类型的操作。

    Task: 代表一个无返回值的操作。

    void: 为了和传统的事件处理程序兼容而设计。

    await(等待)

    await等待的是什么? 可以是一个异步操作(Task)、亦或者是具备返回值的异步操作(Task<TResult>)的值, 如下:

            public async static void GetInfoAsync()
            {
                await GetData(); // 等待异步操作, 无返回值
                await GetData<int>(1); //等待异步操作, 返回值 int
            }
    
            static Task GetData()
            {
                //...
                return null;
            }
    
            static Task<T> GetData<T>(int a)
            {
                //...
                return null;
            }

    注: await 最终操作的是一个值, 当然, 也可以是无值,  如上GetData() , 否则就是一个 Task<T>  如上:  GetData<T>()

    await执行过程

    TaskAwaiter 获取执行结果

    一般而言, await等待的一个异步操作, 无论是具备返回值还是否, 那么最终都会获得该操作是否已完成、具备返回值得异步操作可以获取他得返回结果。

    所以这个时候, TaskAwaiter出现了, 无论是Task、还是Task<TResult>操作, 都具备GetAwaiter() 方法。

    用于获取改操作得状态、返回结果, 及部分操作, 如下TaskAwaiter结构: 

        //
        // 摘要:
        //     提供等待异步任务完成的对象。
        public struct TaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion
        {
            //
            // 摘要:
            //     获取一个值,该值指示是否已完成的异步任务。
            //
            // 返回结果:
            //     true 如果任务已完成;否则为 false。
            //
            // 异常:
            //   T:System.NullReferenceException:
            //     System.Runtime.CompilerServices.TaskAwaiter 对象未正确初始化。
            public bool IsCompleted { get; }
    
            //
            // 摘要:
            //     结束异步任务完成之前的等待。
            //
            // 异常:
            //   T:System.NullReferenceException:
            //     System.Runtime.CompilerServices.TaskAwaiter 对象未正确初始化。
            //
            //   T:System.Threading.Tasks.TaskCanceledException:
            //     任务已取消。
            //
            //   T:System.Exception:
            //     在完成的任务 System.Threading.Tasks.TaskStatus.Faulted 状态。
            [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
            public void GetResult();
            //
            // 摘要:
            //     设置时应执行的操作 System.Runtime.CompilerServices.TaskAwaiter 对象停止等待异步任务完成。
            //
            // 参数:
            //   continuation:
            //     要在等待操作完成时执行的操作。
            //
            // 异常:
            //   T:System.ArgumentNullException:
            //     continuation 为 null。
            //
            //   T:System.NullReferenceException:
            //     System.Runtime.CompilerServices.TaskAwaiter 对象未正确初始化。
            [SecuritySafeCritical]
            public void OnCompleted(Action continuation);
            //
            // 摘要:
            //     计划程序与此等待异步任务的延续任务操作。
            //
            // 参数:
            //   continuation:
            //     要等待操作完成时调用的操作。
            //
            // 异常:
            //   T:System.ArgumentNullException:
            //     continuation 为 null。
            //
            //   T:System.InvalidOperationException:
            //     该等待程序未正确初始化。
            [SecurityCritical]
            public void UnsafeOnCompleted(Action continuation);
        }

     接下来, 演示如何通过等待去获取异步操作的返回结果, 如下代码所示:

            public async static void GetInfoAsync()
            {
                Task<bool> task = Task.Run<bool>(() =>
                {
                    Thread.Sleep(10000); //模拟耗时
                    return true;
                });
                
                //以下两种方式
                bool taskResult1 = await task;  //内部自己执行了GetAwaiter() 
                bool taskResult = task.GetAwaiter().GetResult();  //自己手动执行Awaiter(), 但是阻塞UI
           Console.WriteLine(taskResult);
    }

      注: 对于一个await表达式, 编译器生成的代码会先调用GetAwaiter(), 然后通过awaiter得成员来等待结果, 所以以上两种方式等效( 不考虑阻塞的情况下)

    为了验证以上猜测, 通过反编译工具查看得到如下代码:

    编译器最终生成两个密封类, 一个类( <>c )我们展开分析:

    <GetInfoAsync>b__1_0()  正是模拟耗时的一个操作委托生成的方法。 

            [CompilerGenerated]
            [Serializable]
            private sealed class <>c
            {
                public static readonly Program.<>c <>9 = new Program.<>c();
                public static Func<bool> <>9__1_0;
                internal bool <GetInfoAsync>b__1_0()
                {
                    Thread.Sleep(10000);
                    return true;
                }
            }

    第二个类 <GetInfoAsync>d__1 分析:

    该类分别实现了接口  IAsyncStateMachine 的MoveNext() 与 SetStateMachine() ,另外 注意,

    还特别定义了一个 <>t__builder, 先记住他, 下面讲会对他讲到, 为什么编译器生成的代码会默认先调用GetAwaiter()

     1 [CompilerGenerated]
     2         private sealed class <GetInfoAsync>d__1 : IAsyncStateMachine
     3         {
     4             public int <>1__state;
     5             public AsyncVoidMethodBuilder <>t__builder;
     6             private Task<bool> <task>5__1;
     7             private bool <result>5__2;
     8             private bool <>s__3;
     9             private TaskAwaiter<bool> <>u__1;
    10             void IAsyncStateMachine.MoveNext()
    11             {
    12                 int num = this.<>1__state;
    13                 try
    14                 {
    15                     TaskAwaiter<bool> awaiter;
    16                     if (num != 0)
    17                     {
    18                         Func<bool> arg_2F_0;
    19                         if ((arg_2F_0 = Program.<>c.<>9__1_0) == null)
    20                         {
    21                             arg_2F_0 = (Program.<>c.<>9__1_0 = new Func<bool>(Program.<>c.<>9.<GetInfoAsync>b__1_0));
    22                         }
    23                         this.<task>5__1 = Task.Run<bool>(arg_2F_0);
    24                         awaiter = this.<task>5__1.GetAwaiter();
    25                         if (!awaiter.IsCompleted)
    26                         {
    27                             this.<>1__state = 0;
    28                             this.<>u__1 = awaiter;
    29                             Program.<GetInfoAsync>d__1 <GetInfoAsync>d__ = this;
    30                             this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<bool>, Program.<GetInfoAsync>d__1>(ref awaiter, ref <GetInfoAsync>d__);
    31                             return;
    32                         }
    33                     }
    34                     else
    35                     {
    36                         awaiter = this.<>u__1;
    37                         this.<>u__1 = default(TaskAwaiter<bool>);
    38                         this.<>1__state = -1;
    39                     }
    40                     this.<>s__3 = awaiter.GetResult();
    41                     this.<result>5__2 = this.<>s__3;
    42                     Console.WriteLine(this.<result>5__2);
    43                 }
    44                 catch (Exception exception)
    45                 {
    46                     this.<>1__state = -2;
    47                     this.<>t__builder.SetException(exception);
    48                     return;
    49                 }
    50                 this.<>1__state = -2;
    51                 this.<>t__builder.SetResult();
    52             }
    53             [DebuggerHidden]
    54             void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    55             {
    56             }
    57         }
    View Code

    接下来, 看GetInfoAsync()方法, 这个是自己编写的, 但是实现的细节,最终转换成了编译器执行代码:

            [AsyncStateMachine(typeof(Program.<GetInfoAsync>d__1)), DebuggerStepThrough]
            public static void GetInfoAsync()
            {
                Program.<GetInfoAsync>d__1 <GetInfoAsync>d__ = new Program.<GetInfoAsync>d__1();
                <GetInfoAsync>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
                <GetInfoAsync>d__.<>1__state = -1;
                AsyncVoidMethodBuilder <>t__builder = <GetInfoAsync>d__.<>t__builder;
                <>t__builder.Start<Program.<GetInfoAsync>d__1>(ref <GetInfoAsync>d__); //注意到该代码, 调用了Start(),也许这就是默认实现的地方
            }

    通过查看Start泛型方法的实现, 最终找到了, 该泛型的条件限制于必须实现与 IAsyncStateMachine 接口, 所以通过查看, 该类最终调用了 MoveNext(), 而MoveNext中正

    调用了GetAwaiter()。关于Start的实现如下所示:

    [SecuritySafeCritical, DebuggerStepThrough, __DynamicallyInvokable]
    public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
    {
        if (stateMachine == null)
        {
            throw new ArgumentNullException("stateMachine");
        }
        ExecutionContextSwitcher executionContextSwitcher = default(ExecutionContextSwitcher);
        RuntimeHelpers.PrepareConstrainedRegions();
        try
        {
            ExecutionContext.EstablishCopyOnWriteScope(ref executionContextSwitcher);
            stateMachine.MoveNext();
        }
        finally
        {
            executionContextSwitcher.Undo();
        }
    }

    剖析MoveNext

     对比IDE中的代码, 如下所示:

    总结

    await等待的是任务的操作值, 最终返回是异步操作的返回结果。而这一切都是因为编译器创建了一系列复杂的状态机制, 以达到其实现。

  • 相关阅读:
    Mybatis学习随笔3
    Mybatis学习随笔2
    Mybatis学习随笔
    Java校招面试-什么是线程安全/不安全
    装饰器2
    装饰器
    默认传参的陷阱
    处理日志文件
    第二天
    用户登录
  • 原文地址:https://www.cnblogs.com/zh7791/p/9951478.html
Copyright © 2020-2023  润新知