写点什么

深入解析 C#异步编程:await 关键字背后的实现原理

  • 2024-12-26
    福建
  • 本文字数:4994 字

    阅读完需:约 16 分钟

在 C#中,async 和 await 关键字用于编写异步代码。本文将详细介绍 await 的实现原理,包括状态机的生成、回调函数的注册和触发等关键步骤。


1. 异步方法的基本概念


在 C#中,async 关键字标记一个方法为异步方法,而 await 关键字用于等待一个异步操作完成。异步方法可以提高程序的响应性和性能,特别是在处理 I/O 操作和网络请求时。


2. 示例异步方法


我们以一个简单的异步方法为例,来详细解释 await 的实现原理。


public class Example{    public async Task<int> CalculateAsync()    {        int a = await Task.Run(() => 10);        int b = await Task.Run(() => 20);        return a + b;    }}
复制代码


3. 编译器生成的状态机


编译器会为每个异步方法生成一个状态机。状态机是一个结构体,包含了异步方法的所有局部变量和状态信息。


编译器生成的状态机类


public class Example{    public Task<int> CalculateAsync()    {        <CalculateAsync>d__0 stateMachine = new <CalculateAsync>d__0();        stateMachine.<>4__this = this;        stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();        stateMachine.<>1__state = -1;        stateMachine.<>t__builder.Start(ref stateMachine);        return stateMachine.<>t__builder.Task;    }
[StructLayout(LayoutKind.Auto)] [AsyncMethodBuilder(typeof(AsyncTaskMethodBuilder<int>))] private struct <CalculateAsync>d__0 : IAsyncStateMachine { public int <>1__state; public AsyncTaskMethodBuilder<int> <>t__builder; public Example <>4__this; public int <a>5__1; public TaskAwaiter<int> <>u__1;
private void MoveNext() { int num = <>1__state; try { TaskAwaiter<int> awaiter; switch (num) { case 0: goto TR_0000; case 1: <>1__state = -1; awaiter = <>u__1; <>u__1 = default(TaskAwaiter<int>); goto TR_0001; case 2: <>1__state = -1; break; default: <>1__state = 0; awaiter = Task.Run<int>(() => 10).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); return; } goto TR_0000; } TR_0000: <a>5__1 = awaiter.GetResult(); awaiter = Task.Run<int>(() => 20).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 1); <>u__1 = awaiter; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); return; } TR_0001: int b = awaiter.GetResult(); int result = <a>5__1 + b; <>1__state = -2; <>t__builder.SetResult(result); } catch (Exception exception) { <>1__state = -2; <>t__builder.SetException(exception); } }
[DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } }}
复制代码


4. 实现流程详解


初始化状态机


在 CalculateAsync 方法中,创建状态机实例 <CalculateAsync>d__0


<CalculateAsync>d__0 stateMachine = new <CalculateAsync>d__0();stateMachine.<>4__this = this;stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();stateMachine.<>1__state = -1;
复制代码


  • <>4__this:指向当前实例,即 Example 类的实例。

  • <>t__builder:创建 AsyncTaskMethodBuilder<int> 实例,用于管理任务的生命周期。

  • <>1__state:初始化状态为 -1,表示方法尚未开始执行。


开始执行


调用 Start 方法开始执行异步方法。Start 方法会调用状态机的 MoveNext 方法。


stateMachine.<>t__builder.Start(ref stateMachine);
复制代码


执行方法体


在 MoveNext 方法中,根据当前状态 <>1__state 执行相应的代码。


private void MoveNext(){    int num = <>1__state;    try    {        TaskAwaiter<int> awaiter;        switch (num)        {            // 处理不同的状态        }    }    catch (Exception exception)    {        <>1__state = -2;        <>t__builder.SetException(exception);    }}
复制代码


遇到 await


遇到第一个 await 关键字时,调用 Task.Run(() => 10).GetAwaiter() 获取 Awaiter 对象。


awaiter = Task.Run<int>(() => 10).GetAwaiter();
复制代码


  • 检查 awaiter.IsCompleted,如果任务已经完成,直接调用 awaiter.GetResult() 获取结果。

  • 如果任务未完成,记录当前状态 <>1__state,保存 awaiter 对象,并调用 <>t__builder.AwaitUnsafeOnCompleted 注册回调。


if (!awaiter.IsCompleted){    num = (<>1__state = 0);    <>u__1 = awaiter;    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);    return;}
复制代码


注册回调


AwaitUnsafeOnCompleted 方法会注册一个回调,当任务完成时,回调会被触发。


public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)    where TAwaiter : ICriticalNotifyCompletion    where TStateMachine : IAsyncStateMachine{    awaiter.UnsafeOnCompleted(stateMachine.MoveNext);}
复制代码


  • awaiter.UnsafeOnCompleted 方法注册一个回调函数,该回调函数会在任务完成时被触发。

  • stateMachine.MoveNext 是一个委托,指向状态机的 MoveNext 方法。


任务完成


当任务完成时,回调会被触发,重新调用 MoveNext 方法,恢复异步方法的执行。


public void OnCompleted(Action continuation){    task.ContinueWith(_ => continuation(), TaskScheduler.Default);}
复制代码


继续执行


从上次暂停的地方继续执行方法体。


TR_0000:<a>5__1 = awaiter.GetResult();awaiter = Task.Run<int>(() => 20).GetAwaiter();if (!awaiter.IsCompleted){    num = (<>1__state = 1);    <>u__1 = awaiter;    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);    return;}
复制代码


  • 遇到第二个 await 关键字时,重复上述步骤。


方法完成


当所有异步操作完成并计算出结果后,设置状态 <>1__state 为 -2,表示方法已经完成。


int b = awaiter.GetResult();int result = <a>5__1 + b;<>1__state = -2;<>t__builder.SetResult(result);
复制代码


  • 调用 <>t__builder.SetResult 设置任务的结果。

  • 如果在执行过程中抛出异常,捕获异常并调用 <>t__builder.SetException 设置任务的异常。


catch (Exception exception){    <>1__state = -2;    <>t__builder.SetException(exception);}
复制代码


5. 深入理解 AsyncTaskMethodBuilder


AsyncTaskMethodBuilder 是一个辅助类,用于构建和管理异步方法的任务。它提供了以下方法:

  • Create:创建一个新的 AsyncTaskMethodBuilder 实例。

  • Start:开始执行异步方法,调用状态机的 MoveNext 方法。

  • AwaitUnsafeOnCompleted:注册回调函数,当任务完成时触发回调。

  • SetResult:设置任务的结果。

  • SetException:设置任务的异常。


AsyncTaskMethodBuilder 的内部实现


AsyncTaskMethodBuilder 内部维护了一个 Task 对象,用于表示异步操作的结果。当异步方法完成时,SetResult 方法会设置任务的结果,SetException 方法会设置任务的异常。


public struct AsyncTaskMethodBuilder<TResult>{    private Task<TResult> task;
public static AsyncTaskMethodBuilder<TResult> Create() { return new AsyncTaskMethodBuilder<TResult>(new Task<TResult>()); }
private AsyncTaskMethodBuilder(Task<TResult> task) { this.task = 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); }
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { awaiter.UnsafeOnCompleted(stateMachine.MoveNext); }
public void SetResult(TResult result) { task.SetResult(result); }
public void SetException(Exception exception) { task.SetException(exception); }
public Task<TResult> Task => task;}
复制代码


6. 异步方法的生命周期


异步方法的生命周期可以分为以下几个阶段:

  1. 初始化:创建状态机实例,初始化状态和任务构建器。

  2. 开始执行:调用 Start 方法开始执行异步方法。

  3. 执行方法体:在 MoveNext 方法中,根据当前状态执行相应的代码。

  4. 遇到 await:检查任务是否完成,如果未完成则注册回调并暂停方法执行。

  5. 任务完成:回调被触发,重新调用 MoveNext 方法,恢复异步方法的执行。

  6. 方法完成:所有异步操作完成,设置任务的结果或异常。


7. 异步方法的优势


使用 async 和 await 编写的异步方法有以下优势:

  • 提高响应性:异步方法不会阻塞主线程,应用程序可以继续响应用户的输入和其他事件。

  • 提高性能:异步方法可以并发执行多个任务,充分利用系统资源。

  • 简化代码:异步方法的代码结构类似于同步方法,易于理解和维护。


8. 异步方法的注意事项


尽管 async 和 await 提供了许多优势,但在使用时也需要注意以下几点:

  • 避免 async voidasync void 方法主要用于事件处理程序,其他情况下应避免使用,因为它无法被等待,并且异常处理较为困难。

  • 异常处理:异步方法中的异常会被包装在 AggregateException 中,需要特殊处理。

  • 资源管理:异步方法中使用 using 语句时,需要注意 Dispose 方法的调用时机。


9. 完整的流程图


为了更好地理解这个过程,可以用流程图来展示:



总结


通过上述详细的解释和示例代码,我们可以总结出以下几点:

  1. 异步方法的基本概念async 和 await 关键字用于编写异步代码。

  2. 状态机的生成:编译器为每个异步方法生成一个状态机,包含所有局部变量和状态信息。

  3. MoveNext 方法的执行MoveNext 方法是状态机的核心,负责管理和执行异步操作。

  4. 回调函数的注册和触发:当遇到 await 关键字时,编译器会生成代码来检查任务是否已经完成。如果任务未完成,注册回调并暂停方法执行。当任务完成时,回调函数会被触发,重新调用状态机的 MoveNext 方法,从而恢复异步方法的执行。

  5. AwaitUnsafeOnCompleted 方法的作用:在任务完成时注册一个回调函数,回调函数会在任务完成后被触发,从而恢复异步方法的执行。


文章转载自:极客Bob

原文链接:https://www.cnblogs.com/Bob-luo/p/18518463

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
深入解析C#异步编程:await 关键字背后的实现原理_php_快乐非自愿限量之名_InfoQ写作社区