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

 更新时间:2022年01月26日 11:48:17   作者:痕迹g  
本文详细讲解了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()

 [CompilerGenerated]
         private sealed class <GetInfoAsync>d__1 : IAsyncStateMachine
         {
             public int <>1__state;
             public AsyncVoidMethodBuilder <>t__builder;
             private Task<bool> <task>5__1;
             private bool <result>5__2;
             private bool <>s__3;
             private TaskAwaiter<bool> <>u__1;
             void IAsyncStateMachine.MoveNext()
             {
                 int num = this.<>1__state;
                 try
                 {
                     TaskAwaiter<bool> awaiter;
                     if (num != 0)
                     {
                         Func<bool> arg_2F_0;
                         if ((arg_2F_0 = Program.<>c.<>9__1_0) == null)
                         {
                             arg_2F_0 = (Program.<>c.<>9__1_0 = new Func<bool>(Program.<>c.<>9.<GetInfoAsync>b__1_0));
                         }
                         this.<task>5__1 = Task.Run<bool>(arg_2F_0);
                         awaiter = this.<task>5__1.GetAwaiter();
                         if (!awaiter.IsCompleted)
                         {
                             this.<>1__state = 0;
                             this.<>u__1 = awaiter;
                             Program.<GetInfoAsync>d__1 <GetInfoAsync>d__ = this;
                             this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<bool>, Program.<GetInfoAsync>d__1>(ref awaiter, ref <GetInfoAsync>d__);
                             return;
                         }
                     }
                     else
                     {
                         awaiter = this.<>u__1;
                         this.<>u__1 = default(TaskAwaiter<bool>);
                         this.<>1__state = -1;
                     }
                     this.<>s__3 = awaiter.GetResult();
                     this.<result>5__2 = this.<>s__3;
                     Console.WriteLine(this.<result>5__2);
                 }
                 catch (Exception exception)
                 {
                     this.<>1__state = -2;
                     this.<>t__builder.SetException(exception);
                     return;
                 }
                 this.<>1__state = -2;
                 this.<>t__builder.SetResult();
             }
             [DebuggerHidden]
             void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
             {
             }
         }

接下来, 看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等待的是任务的操作值, 最终返回是异步操作的返回结果。而这一切都是因为编译器创建了一系列复杂的状态机制, 以达到其实现。

到此这篇关于C#异步编程async/await用法详解的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • C#中List集合使用Max()方法查找到最大值的实例

    C#中List集合使用Max()方法查找到最大值的实例

    这篇文章主要介绍了C#中List集合使用Max()方法查找到最大值的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Unity存储游戏数据的多种方法小结

    Unity存储游戏数据的多种方法小结

    这篇文章主要介绍了Unity存储游戏数据的几种方法,在游戏开发中,存储游戏数据是非常重要的,因为游戏数据决定了游戏的各个方面,例如游戏的进度、玩家的成就、游戏的设置,需要的朋友可以参考下
    2023-02-02
  • c#注册表操作类分享

    c#注册表操作类分享

    这篇文章主要介绍了c#注册表操作类,主要包括创建注册表项、读取注册表项、判断注册表项是否存在、删除注册表项、创建注册表键值 、读取注册表键值、判断注册表键值是否存在、删除注册表键值等功能,需要的朋友可以参考下
    2014-03-03
  • C#实现Word转PDF的方法总结

    C#实现Word转PDF的方法总结

    这篇文章主要为大家详细介绍了C#中实现Word转PDF的常用方法,文中的示例代码讲解详细,具有一定的学习价值,有需要的小伙伴可以参考下
    2023-10-10
  • C#调用摄像头实现拍照功能的示例代码

    C#调用摄像头实现拍照功能的示例代码

    这篇文章主要介绍了C#调用摄像头实现拍照功能的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 解决winform界面刷新闪烁问题的多种方案

    解决winform界面刷新闪烁问题的多种方案

    这篇文章给大家介绍了如何解决winform界面刷新闪烁问题的多种方案,文中通过代码示例给大家介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-02-02
  • C#利用delegate实现Javascript的each方法

    C#利用delegate实现Javascript的each方法

    这篇文章主要为大家介绍了介绍了C#利用delegate实现Javascript的each方法,感兴趣的朋友可以参考一下
    2016-01-01
  • C#自定义RSA加密解密及RSA签名和验证类实例

    C#自定义RSA加密解密及RSA签名和验证类实例

    这篇文章主要介绍了C#自定义RSA加密解密及RSA签名和验证类,实例分析了C#实现RSA加密解密及RSA签名和验证的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-03-03
  • C#发送内置图片html格式邮件的方法

    C#发送内置图片html格式邮件的方法

    这篇文章主要介绍了C#发送内置图片html格式邮件的方法,涉及C#发送邮件的技巧,非常具有实用价值,需要的朋友可以参考下
    2015-04-04
  • C# String Replace高效的实例方法

    C# String Replace高效的实例方法

    C# String Replace高效的实例方法,需要的朋友可以参考一下
    2013-05-05

最新评论