服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C#异步编程async/await用法详解

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

2022-12-22 14:41痕迹g C#

本文详细讲解了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执行过程

C#异步编程async/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#异步编程async/await用法详解

编译器最终生成两个密封类, 一个类( <>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

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

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

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

 

总结

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

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

原文链接:https://www.cnblogs.com/zhaoshujie/p/11192036.html

延伸 · 阅读

精彩推荐
  • C#WPF TextBox实现按字节长度限制输入功能

    WPF TextBox实现按字节长度限制输入功能

    这篇文章主要为大家详细介绍了WPF TextBox实现按字节长度限制输入功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    衆尋8272022-01-25
  • C#C#加载嵌入到资源的非托管dll

    C#加载嵌入到资源的非托管dll

    很多朋友遇到过这样一个问题需要加载非win32的非托管dll,纠结该怎么操作呢,下面通过代码给大家介绍下C#加载嵌入到资源的非托管dll的方法,感兴趣的朋...

    RyzenAdorer4302022-11-17
  • C#C#实现将PDF转为线性化PDF

    C#实现将PDF转为线性化PDF

    线性化PDF文件是PDF文件的一种特殊格式,可以通过Internet更快地进行查看。这篇文章主要介绍了如何通过C#实现将PDF转为线性化PDF,感兴趣的小伙伴可以学习...

    E-iceblue8792022-12-14
  • C#C#创建dll类库的图文步骤

    C#创建dll类库的图文步骤

    类库让我们的代码可复用,我们只需要在类库中声明变量一次,就能在接下来的过程中无数次地使用,而无需在每次使用前都要声明它。这样一来,就节省...

    C#教程网11802021-12-21
  • C#C# 忽略大小写进行字符串比较

    C# 忽略大小写进行字符串比较

    这篇文章主要介绍了C# 字符串比较忽略大小写的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...

    许鸿飞10122022-11-01
  • C#C# Winform自动更新程序实例详解

    C# Winform自动更新程序实例详解

    这篇文章主要为大家详细介绍了C# Winform 自动更新程序实例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    garychk4282022-02-13
  • C#C#做线形图的方法

    C#做线形图的方法

    在本篇内容中小编给大家总结了C#怎么做线形图的教程内容,对此有需要的朋友们可以跟着学习下。...

    C#教程网9242022-03-07
  • C#C#限速下载网络文件的方法实例

    C#限速下载网络文件的方法实例

    本篇文章主要介绍了C#限速下载网络文件的方法实例,可以限制下载文件的速度,非常具有实用价值,需要的朋友可以参考下。...

    秋荷雨翔12092021-12-15