【问题标题】:implementing a state machine using the "yield" keyword使用“yield”关键字实现状态机
【发布时间】:2010-11-14 18:09:18
【问题描述】:

使用yield关键字实现简单状态机as shown here是否可行。在我看来,C# 编译器似乎已经为您完成了艰苦的工作,因为它在内部实现了一个状态机来使 yield 语句工作。

您能否利用编译器已经在做的工作并让它为您实现大部分状态机?

有人做过吗,技术上可行吗?

【问题讨论】:

    标签: c# yield state-machine fsm


    【解决方案1】:

    这是可行的,但这是一个坏主意。创建迭代器块是为了帮助您为集合编写自定义迭代器,而不是解决实现状态机的通用问题。

    如果你想写一个状态机,就写一个状态机。这并不难。如果您想编写大量状态机,请编写一个有用的帮助方法库,让您清楚地表示状态机,然后使用您的库。但是不要滥用一种语言结构,该结构旨在用于完全不同的东西,而恰好使用状态机作为实现细节。这使您的状态机代码难以阅读、理解、调试、维护和扩展。

    (顺便说一句,当我读到你的名字时,我做了一个双重考虑。C# 的设计者之一也叫 Matt Warren!)

    【讨论】:

    • 是的,我不久前发现了他的博客,当你找到同名的人时总是很奇怪!我确实认为这可能有点滥用迭代器块,这就是我想先检查的原因。
    • 糟糕。抱歉,我严重滥用了语言(我在编写示例时确实感到痛苦)!但我认为 FSM(尤其是无交互)和“生成序列”之间有很多重叠之处。例如,我认为它适用于您想要在某些输入中找到匹配模式的场景,比如RegEx;本质上,将其用作 DFA 之类的东西。您对此有何看法?
    • 我认为将 FSM 视为从输入序列到状态序列的映射的一般想法并不是那么糟糕的想法。如果这就是您构想 FSM 的方式,那么使用迭代器块并不是一个糟糕的主意。但是,如果您要这样做,那么在某种对象中捕获“规则”是有意义的,该对象可以有效地从(状态,输入)-> 状态进行映射。这样,您就可以在一个对象中捕获 FSM 逻辑,您可以在调试器中对其进行检查,而不是在 IL 中很难看到它。
    • 我认为任何类型的非平凡 FSM 都可能与显式 FSM 最匹配,但 FSM 越简单,迭代器块就越好。简单的 if-else 似乎是这种用法的一个不错的选择,因为在我看来,显式 FSM 比 if-else 代码块更笨拙且更难阅读。
    【解决方案2】:

    是的,这绝对是可能的,而且很容易做到。您可以享受使用控制流构造(forforeachwhile、...goto(使用goto 特别适合这种情况;)))以及yields 来构建一个。

    IEnumerator<State> StateMachine
                 (Func<int> currentInput /* gets current input from IO port */, 
                  Func<int> currentOutput) {
        for (;;)  {
           if ((currentInput() & 1) == 0) 
               yield return new State("Ready"); 
           else {
               if (...) {
                   yield return new State("Expecting more data");
                   SendOutput(currentOutput());
                   while ((currentInput() & 2) != 0) // while device busy
                        yield return new State("Busy");
               else if (...) { ... } 
           }
        }
    }
    
    // consumer:
    int data;
    var fsm = StateMachine(ReadFromIOPort, () => data);
    // ...
    while (fsm.Current != "Expecting more data")
        fsm.MoveNext();
    data = 100;
    fsm.MoveNext();
    

    【讨论】:

    • 也许我的想法不正确,但我看不出你会如何在代码中做到这一点,你有一个可以让我开始的例子吗?
    • 如果在现实生活中看到这提供了比显式状态机更简洁的代码,我会感到有些惊讶。
    • 乔恩:我必须同意。它很快就会变得过于复杂。无论如何,如果状态机大部分是自动的,它会工作得很好。
    • 我确信在某些情况下这样的迭代是可以的——但是你有一个状态机,它现在只为循环行为而连接。如果ReadFromIOPort 延迟、端口意外关闭或端口正忙于其他事情,此状态机会发生什么情况?如果要设置接收数据的超时时间怎么办?必须改变机器的循环特性以处理任何问题。
    【解决方案3】:

    迭代器块确实实现了状态机,但棘手的是获取下一个输入。你怎么知道下一步要去哪里?我猜你可能有某种共享的“当前转换”变量,但这有点恶心。

    如果您不需要任何输入(例如,您的状态机只是在状态之间循环),那么这很容易,但这不是有趣的类型 :)

    您能描述一下您感兴趣的状态机类型吗?

    【讨论】:

    • 即使有输入,也可以轻松使用捕获的变量。这有点难看,但我认为不手动实现它是值得的。
    • 我需要来自事件处理程序的输入,这些事件处理程序连接起来以响应来自外部硬件的 I/O 信号。
    • @Mehrdad:我想我宁愿编写自己的状态机,也不愿使用捕获的变量作为输入形式……尤其是在状态转换非常复杂的情况下。
    【解决方案4】:

    虽然这不是经典意义上的状态机,但关于 Iterator-based Micro Threading 的文章创造性地使用了 yield 来实现基于状态的操作。

    IEnumerable Patrol ()
    {
        while (alive){
            if (CanSeeTarget ()) {
                yield return Attack ();
            } else if (InReloadStation){
                Signal signal = AnimateReload ();
                yield return signal;
            } else {
                MoveTowardsNextWayPoint ();
                yield return TimeSpan.FromSeconds (1);
            };
        }
        yield break;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-08-25
      • 2015-10-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-25
      • 2011-03-31
      • 1970-01-01
      相关资源
      最近更新 更多