首页 > 编程语言 >聊一聊 C#异步 任务延续的三种底层玩法

聊一聊 C#异步 任务延续的三种底层玩法

时间:2025-01-09 17:30:16浏览次数:3  
标签:case 异步 Task return continuationObject C# html 聊一聊 var

一:背景

1. 讲故事

最近聊了不少和异步相关的话题,有点疲倦了,今天再写最后一篇作为近期这类话题的一个封笔吧,下篇继续写我熟悉的 生产故障 系列,突然亲切感油然而生,哈哈,免费给别人看程序故障,是一种积阴德阳善的事情,欲知前世因,今生受者是。欲知来世果,今生做者是。

在任务延续方面,我个人的总结就是三类,分别为:

  1. StateMachine
  2. ContinueWith
  3. Awaiter

话不多说,我们逐个研究下底层是咋玩的?

二:异步任务延续的玩法

1. StateMachine

说到状态机大家再熟悉不过了,也是 async,await 的底层化身,很多人看到 async await 就想到了IO场景,其实IO场景和状态机是两个独立的东西,状态机是一种设计模式,把这个模式套在IO场景会让代码更加丝滑,仅此而已。为了方便讲述,我们写一个 StateMachine 与 IO场景 无关的一段测试代码。


    internal class Program
    {
        static void Main(string[] args)
        {
            UseAwaitAsync();

            Console.ReadLine();
        }

        static async Task<string> UseAwaitAsync()
        {
            var html = await Task.Run(() =>
            {
                Thread.Sleep(1000);
                var response = "<html><h1>博客园</h1></html>";
                return response;
            });
            Console.WriteLine($"GetStringAsync 的结果:{html}");
            return html;
        }
    }

那这段代码在底层是如何运作的呢?刚才也说到了asyncawait只是迷惑你的一种幻象,我们必须手握辟邪宝剑斩开幻象显真身,这里借助 ilspy 截图如下:

从卦中看,本质上就是借助AsyncTaskMethodBuilder<string> 建造者将 awaiter 和 stateMachine 做了一个绑定,感兴趣的朋友可以追一下 AwaitUnsafeOnCompleted() 方法,最后状态机 <UseAwaitAsync>d__1 实例会放入到 Task.Run 的 m_continuationObject 字段。如果有朋友对流程比较蒙的话,我画了一张简图。

图和代码都有了,接下来就是眼见为实。分别在 AddTaskContinuation 和 RunContinuations 方法中做好埋点,前者可以看到 延续任务 是怎么加进去的,后者可以看到 延续任务 是怎么取出来的。

心细的朋友会发现这卦上有一个很特别的地方,就是 allowInlining=true,也就是回调函数(StateMachine)是在当前线程上一撸到底的。

有些朋友可能要问,能不能让延续任务 跑在单独线程上? 可以是可以,但你得把 Task.Run 改成 Task.Factory.StartNew ,这样就可以设置TaskCreationOptions参数,参考代码如下:

    var html = await Task.Factory.StartNew(() =>{}, TaskCreationOptions.RunContinuationsAsynchronously);

2. ContinueWith

那些同处于被裁的35岁大龄程序员应该知道Task是 framework 4.0 时代出来的,而async,await是4.5出来的,所以在这个过渡期中有大量的项目会使用ContinueWith 导致回调地狱。。。 这里我们对比一下两者有何不同,先写一段参考代码。


    internal class Program
    {
        static void Main(string[] args)
        {
            UseContinueWith();

            Console.ReadLine();
        }

        static Task<string> UseContinueWith()
        {
            var query = Task.Run(() =>
            {
                Thread.Sleep(1000);
                var response = "<html><h1>博客园</h1></html>";
                return response;
            }).ContinueWith(t =>
            {
                var html = t.Result;
                Console.WriteLine($"GetStringAsync 的结果:{html}");
                return html;
            });

            return query;
        }
    }

从卦代码看确实没有asyncawait简洁,那 ContinueWith 内部做了什么呢?感兴趣的朋友可以跟踪一下,本质上和 StateMachine 的玩法是一样的,都是借助 m_continuationObject 来实现延续,画个简图如下:

代码和模型图都有了,接下来就是用 dnspy 开干了。。。还是在 AddTaskContinuation 和 RunContinuations 上埋伏断点观察。

从卦中可以看到,延续任务使用新线程来执行的,并没有一撸到底,这明显与 asyncawait 的方式不同,有些朋友可能又要说了,那如何实现和StateMachine一样的呢?这就需要在 ContinueWith 中新增 ExecuteSynchronously 同步参数,参考如下:

    var query = Task.Run(() => { }).ContinueWith(t =>
    {
    }, TaskContinuationOptions.ExecuteSynchronously);

3. Awaiter

使用Awaiter做任务延续的朋友可能相对少一点,它更多的是和 StateMachine 打配合,当然单独使用也可以,但没有前两者灵活,它更适合那些不带返回值的任务延续,本质上也是借助 m_continuationObject 字段实现的一套底层玩法,话不多说,上一段代码:


        static Task<string> UseAwaiter()
        {
            var awaiter = Task.Run(() =>
            {
                Thread.Sleep(1000);
                var response = "<html><h1>博客园</h1></html>";
                return response;
            }).GetAwaiter();

            awaiter.OnCompleted(() =>
            {
                var html = awaiter.GetResult();
                Console.WriteLine($"UseAwaiter 的结果:{html}");
            });

            return Task.FromResult(string.Empty);
        }

前面两种我配了图,这里没有理由不配了,哈哈,模型图如下:

接下来把程序运行起来,观察截图:

从卦中观察,它和StateMachine一样,默认都是 一撸到底 的方式。

三:RunContinuations 观察

这一小节我们单独说一下 RunContinuations 方法,因为这里的实现太精妙了,不幸的是Dnspy和ILSpy反编译出来的代码太狗血,原汁原味的简化后代码如下:

    private void RunContinuations(object continuationObject) // separated out of FinishContinuations to enable it to be inlined
    {
        bool canInlineContinuations =
            (m_stateFlags & (int)TaskCreationOptions.RunContinuationsAsynchronously) == 0 &&
            RuntimeHelpers.TryEnsureSufficientExecutionStack();

        switch (continuationObject)
        {
            // Handle the single IAsyncStateMachineBox case.  This could be handled as part of the ITaskCompletionAction
            // but we want to ensure that inlining is properly handled in the face of schedulers, so its behavior
            // needs to be customized ala raw Actions.  This is also the most important case, as it represents the
            // most common form of continuation, so we check it first.
            case IAsyncStateMachineBox stateMachineBox:
                AwaitTaskContinuation.RunOrScheduleAction(stateMachineBox, canInlineContinuations);
                LogFinishCompletionNotification();
                return;

            // Handle the single Action case.
            case Action action:
                AwaitTaskContinuation.RunOrScheduleAction(action, canInlineContinuations);
                LogFinishCompletionNotification();
                return;

            // Handle the single TaskContinuation case.
            case TaskContinuation tc:
                tc.Run(this, canInlineContinuations);
                LogFinishCompletionNotification();
                return;

            // Handle the single ITaskCompletionAction case.
            case ITaskCompletionAction completionAction:
                RunOrQueueCompletionAction(completionAction, canInlineContinuations);
                LogFinishCompletionNotification();
                return;
        }
    }

卦中的 case 挺有意思的,除了本篇聊过的 TaskContinuation 和 IAsyncStateMachineBox 之外,还有另外两种 continuationObject,这里说一下 ITaskCompletionAction 是怎么回事,其实它是 Task.Result 的底层延续类型,所以大家应该能理解为什么 Task.Result 能唤醒,主要是得益于Task.m_continuationObject =completionAction 所致。

说了这么说,如何眼见为实呢?可以从源码中寻找答案。


        private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken cancellationToken)
        {
            var mres = new SetOnInvokeMres();

            AddCompletionAction(mres, addBeforeOthers: true);

            var returnValue = mres.Wait(Timeout.Infinite, cancellationToken);
        }

        private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction
        {
            internal SetOnInvokeMres() : base(false, 0) { }
            public void Invoke(Task completingTask) { Set(); }
            public bool InvokeMayRunArbitraryCode => false;
        }

从卦中可以看到,其实就是把 ITaskCompletionAction 接口的实现类 SetOnInvokeMres 塞入了 Task.m_continuationObject 中,一旦Task执行完毕之后就会调用 Invoke() 下的 Set() 来实现事件唤醒。

四:总结

虽然异步任务延续有三种实现方法,但底层都是一个套路,即借助 Task.m_continuationObject 字段玩出的各种花样,当然他们也是有一些区别的,即对 m_continuationObject 任务是否用单独的线程调度,产生了不同的意见分歧。

关注灵活就业新业态,关注公账号:贤才宝(贤才宝https://www.51xcbw.com)

标签:case,异步,Task,return,continuationObject,C#,html,聊一聊,var
From: https://blog.csdn.net/weixin_60669486/article/details/145038635

相关文章

  • 豆包视觉理解模型主要采用了卷积神经网络(CNN)和变换器(Transformer)架构
    豆包视觉理解模型主要采用了卷积神经网络(CNN)和变换器(Transformer)架构。具体介绍如下:卷积神经网络(CNN)特征提取能力:CNN的卷积层通过卷积核在图像上滑动进行卷积操作,能够自动提取图像的局部特征,如边缘、纹理、形状等。这些特征对于图像的理解和识别至关重要,不同的卷积核可以提取不......
  • 发那科FANUC机器人A06B-0652-B212电机维修基本流程
    发那科FANUC机器人以其卓越的性能和可靠性赢得了广泛的认可。然而,就像其他任何机械设备一样,长时间的运行和复杂的工作环境都可能使伺服电机面临维修的需求。为了确保您的发那科FANUC机器人A06B-0652-B212电机始终保持在最佳状态,发那科伺服电机维修服务显得尤为重要。一、发那科FA......
  • .net core 在Liunx 使用System.Drawing.Common 出现Gdip问题
    问题:当我们使用画图之类的功能可能会引用System.Drawing.Common,或者间接引用它,由于它是依赖Windows系统,所以我们在Windows是正常的,放在Linux就会出现类似于下面这种错误System.TypeInitializationException:Thetypeinitializerfor'Gdip'threwanexception.--->S......
  • Vue项目中,页面ctrl 放大 超出屏幕问题
    现在vue的脚手架生成项目之后我们会发现index.html页面中。在head标签中,我们会看到meta标签中有一条显示是但是我们发现这条语句中只是让user-scalable=0,这是不让用户进行缩放。可以页面会在两个手指进行放大!!!可以页面会在两个手指进行放大!!!可以页面会在两个手指进行放大!!!这就......
  • 705 Air Cownditioning
    //705.cpp:此文件包含"main"函数。程序执行将在此处开始并结束。///*http://oj.daimayuan.top/course/22/problem/283FarmerJohn的N头奶牛对他们牛棚的室温非常挑剔。有些奶牛喜欢温度低一些,而有些奶牛则喜欢温度高一些。FarmerJohn的牛棚包含一排N个牛栏,编号......
  • CDS标准视图:催款级别分配 I_DunningLevelDistribution
    视图名称:催款级别分配I_DunningLevelDistribution视图类型:基础视图视图代码:点击查看代码@AbapCatalog.sqlViewName:'IFIDUNLVLDISTR'@VDM.viewType:#COMPOSITE@EndUserText.label:'DunningLevelDistribution'@Analytics:{dataCategory:#CUBE}@Analytics.intern......
  • docker使用远程节点构建多平台镜像
    当在x86或arm平台使用buildx构建多平台镜像时,会发现buildx在构建与docker所在宿主机异构平台镜像包时,构建速度慢的问题。当你同时有x86和arm服务器的基础上,可以使用远程节点来构建多平台镜像包,使用远程节点构建多平台镜像的好处如下:1.无兼容性问题:在x86平台构建arm或在arm平台构......
  • Expressionable.Create
    Expressionable.Create是一个用于构建动态表达式的方法,通常在使用ORM框架如SqlSugar时使用。它允许开发者在运行时动态地构建查询条件和过滤器等表达式。用法示例以下是一个使用Expressionable.Create构建动态查询条件的示例:csharpvarexp=Expressionable.Create<O......
  • 寒假5分钟搭建MC服务器
    1.搭建前的准备访问雨云-新一代云服务提供商,购买一个服务器来作为后续的准备。雨云-新一代云服务提供商的服务器是十分优惠的!点击游戏云板块,推荐选择MCSM面板,操作简单适合新手选择自己需求的版本,想要装Mod就选择Forge或者Fabric(Forge和Fabric的模组不能混用!)不需要......
  • 基于Simulink的模糊逻辑控制(Fuzzy Logic Control, FLC)的他励直流电动机与永磁直流电动
    目录基于Simulink的模糊逻辑控制(FuzzyLogicControl,FLC)的他励直流电动机与永磁直流电动机模型实例1.项目背景2.系统架构2.1他励直流电动机简介2.2永磁直流电动机简介2.3模糊逻辑控制原理3.模型设计3.1他励直流电动机建模3.2永磁直流电动机建模3.3模糊逻......