RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1315925
Accepted
aepot
aepot
Asked:2022-08-10 04:19:24 +0000 UTC2022-08-10 04:19:24 +0000 UTC 2022-08-10 04:19:24 +0000 UTC

自制状态机 - 没有 async/await 的异步方法

  • 772

有各种反编译异步代码的示例展示了异步状态机的内部。但它看起来很复杂,目前还不清楚它是什么,以及它是如何工作的。

是否可以以一个简单的异步方法为例,在行为上编写完全相同,但没有async/await? 要使用调试器,请逐步了解该方法以了解其工作原理。

比如这个:

private static async Task GoAsync()
{
    for (int i = 1; i <= 10; i++)
    {
        await Task.Delay(200);
        Console.WriteLine(i);
    }
    for (int i = 10; i > 0; i--)
    {
        await Task.Delay(200);
        Console.WriteLine(i);
    }
}

该方法只计算从 1 到 10 和从 10 到 1 的延迟。

发射:

static async Task Main(string[] args)
{
    await GoAsync();
    Console.WriteLine("Done.");
    Console.ReadKey();
}
c#
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Best Answer
    Pavel Mayorov
    2022-08-14T22:14:32Z2022-08-14T22:14:32Z

    我将给出一个更现实的实现。首先,让我们用循环替换所有循环while并对状态进行编号:

    private static async Task GoAsync()
    {
        int i;
    
        /*0*/ i = 1;
        /*1*/ while (i <= 10)
        {
            await Task.Delay(200);
            /*2*/ Console.WriteLine(i);
            i++;
        }
    
        i = 10;
        /*3*/ while (i > 0)
        {
            await Task.Delay(200);
            /*4*/ Console.WriteLine(i);
            i--;
        }
    }
    

    对状态进行编号的条件很简单——您需要对起始点 (0)、可以通过多种方式执行的任何点(1 和 3)以及等待操作符的返回点(2 和 4)进行编号。

    现在我们可以开始编写状态机了:

    class FSM
    {
        private readonly TaskCompletionSource<object> completion = new TaskCompletionSource<object>();
        private TaskAwaiter awaiter;
        private int i;
        private int state = 0;
    
        void Next()
        {
            try
            {
                switch (state)
                {
                    case 0:
                        i = 1;
                        goto case 1;
    
                    case 1:
                        if (i <= 10)
                        {
                            awaiter = Task.Delay(200).GetAwaiter();
                            if (awaiter.IsCompleted) goto case 2;
                            state = 2;
                            awaiter.OnCompleted(Next);
                            break;
                        }
                        i = 10;
                        goto case 3;
    
                    case 2:
                        awaiter.GetResult();
                        Console.WriteLine(i);
                        i++;
                        goto case 1;
    
                    case 3:
                        if (i > 0)
                        {
                            awaiter = Task.Delay(200).GetAwaiter();
                            if (awaiter.IsCompleted) goto case 4;
                            state = 4;
                            awaiter.OnCompleted(Next);
                            break;
                        }
                        state = -1;
                        completion.SetResult(null);
                        break;
    
                    case 4:
                        awaiter.GetResult();
                        Console.WriteLine(i);
                        i--;
                        goto case 3;
                }
            }
            catch (Exception ex)
            {
                state = -1;
                completion.SetException(ex);
            }
        }
    
        public static Task GoAsync()
        {
            var fsm = new FSM();
            fsm.Next();
            return fsm.completion.Task;
        }
    }
    

    生成的状态机大致对应于编译器构建的状态机,但有以下区别:

    1. 编译器completion不会立即创建,而是延迟创建,以免在函数返回时未执行 await 运算符而创建不必要的对象;
    2. 发布模式下的编译器出于相同目的使用结构而不是类;
    3. 编译器缓存 Next 委托;
    4. 编译器记住并恢复执行上下文(ExecutionContext)。
    • 4
  2. aepot
    2022-08-10T04:19:24Z2022-08-10T04:19:24Z

    实现一个自制的状态机有一点困难:它不能完全适应一种方法。除了状态之外,该方法还有变量,在这种情况下i,它必须存储在某个地方。因此,将使用几个附加字段。

    为了不一头扎进手动线程,我将使用线程池ThreadPool,为了管理任务,我将使用TaskCompletionSource. 另外,为了便于实现,我将使用Thread.Sleep它来代替它的异步对应物,否则我将不得不编写另一个状态机并将其与主状态机相关联。这个答案(以及问题)的目的是展示最简单的DIY状态机。

    这是解决方案,在注释中我标记了哪些代码行执行与原始方法相同的功能:

    private static int counter;
    private static TaskCompletionSource tcs;
    
    private static Task GoAsync(int state = 0)
    {
        switch (state)
        {
            case 0:
                tcs = new TaskCompletionSource();
                counter = 1; // for (int i = 1;
                state = 1;
                break;
            case 1:
                if (counter <= 10) // i <= 10;
                {
                    Thread.Sleep(200); // await Task.Delay(200);
                    Console.WriteLine(counter); // Console.WriteLine(i);
                    counter++; // i++)
                }
                else
                    state = 2;
                break;
            case 2:
                counter = 10; // for (int i = 10;
                state = 3;
                break;
            case 3:
                if (counter > 0) // i > 0;
                {
                    Thread.Sleep(200); // await Task.Delay(200);
                    Console.WriteLine(counter); // Console.WriteLine(i);
                    counter--; // i--)
                }
                else
                    state = 4;
                break;
            case 4:
                tcs.SetResult();
                return null;
        }
            
        ThreadPool.QueueUserWorkItem(s => GoAsync(s), state, false);
        return tcs.Task;
    }
    

    这是有限状态机设计模式的实现,或者也称为状态机。

    现在您可以设置断点并在调试器中直观地查看它是如何工作的。让我提醒你,这个实现被大大简化了,并且有它的局限性,例如,如果该方法并行运行两次,一切都会中断。

    • 2

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    表格填充不起作用

    • 2 个回答
  • Marko Smith

    提示 50/50,有两个,其中一个是正确的

    • 1 个回答
  • Marko Smith

    在 PyQt5 中停止进程

    • 1 个回答
  • Marko Smith

    我的脚本不工作

    • 1 个回答
  • Marko Smith

    在文本文件中写入和读取列表

    • 2 个回答
  • Marko Smith

    如何像屏幕截图中那样并排排列这些块?

    • 1 个回答
  • Marko Smith

    确定文本文件中每一行的字符数

    • 2 个回答
  • Marko Smith

    将接口对象传递给 JAVA 构造函数

    • 1 个回答
  • Marko Smith

    正确更新数据库中的数据

    • 1 个回答
  • Marko Smith

    Python解析不是css

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5