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

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

时间:2024-11-01 09:09:49浏览次数:1  
标签:__ 异步 stateMachine C# await state awaiter 方法

C# 异步编程中 await 实现原理详解

在C#中,asyncawait 关键字用于编写异步代码。本文将详细介绍 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. 异步方法的优势

使用 asyncawait 编写的异步方法有以下优势:

  • 提高响应性:异步方法不会阻塞主线程,应用程序可以继续响应用户的输入和其他事件。
  • 提高性能:异步方法可以并发执行多个任务,充分利用系统资源。
  • 简化代码:异步方法的代码结构类似于同步方法,易于理解和维护。

8. 异步方法的注意事项

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

  • 避免 async voidasync void 方法主要用于事件处理程序,其他情况下应避免使用,因为它无法被等待,并且异常处理较为困难。
  • 异常处理:异步方法中的异常会被包装在 AggregateException 中,需要特殊处理。
  • 资源管理:异步方法中使用 using 语句时,需要注意 Dispose 方法的调用时机。

9. 完整的流程图

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

总结

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

  1. 异步方法的基本概念asyncawait 关键字用于编写异步代码。
  2. 状态机的生成:编译器为每个异步方法生成一个状态机,包含所有局部变量和状态信息。
  3. MoveNext 方法的执行MoveNext 方法是状态机的核心,负责管理和执行异步操作。
  4. 回调函数的注册和触发
    • 当遇到 await 关键字时,编译器会生成代码来检查任务是否已经完成。
    • 如果任务未完成,注册回调并暂停方法执行。
    • 当任务完成时,回调函数会被触发,重新调用状态机的 MoveNext 方法,从而恢复异步方法的执行。
  5. AwaitUnsafeOnCompleted 方法的作用:在任务完成时注册一个回调函数,回调函数会在任务完成后被触发,从而恢复异步方法的执行。

希望这些解释能帮助你更好地理解 await 实现原理。如果你还有任何疑问,请随时提问!
详情请看:https://www.cnblogs.com/Bob-luo/p/18518463

希望这篇文章对你有所帮助!如果你有任何进一步的问题或需要更多的细节,请告诉我。

标签:__,异步,stateMachine,C#,await,state,awaiter,方法
From: https://www.cnblogs.com/Bob-luo/p/18518463

相关文章

  • vscode 常用快捷键
    Shift+Alt+FAlt+Up/DownMovelinedown/up:行进行上下Ctrl+Shift+KDeleteline:删掉本行Ctrl+Up/DownScrolllineup/down:按行上行,下行Ctrl+Shift+[/]Fold/Unfoldregion:打开关闭区域Ctrl+KCtrl+[/]Fold/Unfoldallsubregions:打开关闭所有子区域Ctrl......
  • 解决DedeCms连接数据库失败的问题
    错误信息分析错误信息: DedeCms错误警告:连接数据库失败,可能数据库密码不对或数据库服务器出错!可能原因:数据库账号信息错误虚拟主机问题数据库连接超时数据库连接信息在/data/common.inc.php文件中,检查以下数据库连接信息:$cfg_dbname='dbname';//数据库名......
  • 通过Navicat for MySQL恢复数据库的步骤
    启动NavicatforMySQL在本地主机上启动NavicatforMySQL应用程序。创建新的MySQL连接在菜单栏中选择 连接 > MySQL。在弹出的 MySQL-新建连接 对话框中,输入云虚拟主机的数据库信息,包括主机地址、端口、用户名和密码。单击 连接 按钮以建立连接。打开数据......
  • 织梦(DedeCMS)日志文件的位置及查看方法
    日志文件位置织梦(DedeCMS)的日志文件通常存储在网站根目录下的 data/log文件夹中。具体路径如下: /你的网站根目录/data/log/查看日志文件的方法通过FTP客户端查看使用FTP客户端(如FileZilla)登录到您的网站服务器。导航到 data/log 文件夹。在该文件夹中,您会看到......
  • 在Codeigniter中使用Blade模板引擎
    使用compoer引入blade库composerrequire"philo/laravel-blade":"3.*"在helpers目录下创建view_helper.php<?phpif(!defined('BASEPATH'))exit('Nodirectscriptaccessallowed');require_once'vendor/autoload.php';......
  • C语言数据结构之二叉树(BINARY TREE)链式存贮的简单实现
    C语言数据结构之二叉树(BINARYTREE)链式存贮的简单实现树型数据结构在应用中非常多,效率也非常好,只是结构相对复杂,理解起来有点儿难度!!!定义数据结构typedefstruct_BTreeNodeBTreeNode;struct_BTreeNode{intval;BTreeNode*lchild,*rchild;};自定义结构体数......
  • C语言用GNU源码编译建构系统工具(GNU BUILD SYSTEM)编译创建动态库
    C语言用GNU源码编译建构系统工具(GNUBUILDSYSTEM)编译创建动态库首先看一下这篇博文:C语言数据结构之顺序结构(Sequence)此次目的是将sequence.c改造一下,创建为一个动态链接库同时打包一个可发布的源码包,包括源码、头文件、测试文件!创建工作目录工作目录libmg(mg即muggles,一......
  • C语言数据结构之哈希表(HASHTABLE)的实现
    C语言数据结构之哈希表(HASHTABLE)的实现哈希表的每个节点保存的数据格式为key:value,其中key为字符串,根据字符串内容采用不同方法(哈希函数)生成一个无符号整型哈希码,根据表的长度,采用取余法,将数据存入表单元,如果此表单元中已存在数据,则以此表单元为链表头,向链表追加数据,这......
  • Dedecms后台 Fatal error:Allowed memory size of 8388608 bytes 提示的解决方法
    修改 .htaccess 文件在 .htaccess 文件的最上面添加:php_valuemax_execution_time1200php_valuememory_limit200Mphp_valuepost_max_size200Mphp_valueupload_max_filesize200M修改 php.ini 文件将 memory_limit 的值从 8M 改为 12M 或更......
  • 织梦DedeCMS生成静态文件速度缓慢的解决方案
    问题:DedeCMS网站数据量大时,生成静态页面速度非常慢。解决方法:修改 inc_fun_SpGetArcList.php 文件:打开 include/inc/inc_fun_SpGetArcList.php 文件。找到以下代码:for($i=0;$i<$ridnum;$i++){if($tpsql=="")$tpsql.="And((".TypeGetSunID($reids[$i],$......