65.9K
CodeProject 正在变化。 阅读更多。
Home

A .NET 状态机工具包 - 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (32投票s)

2005 年 9 月 18 日

CPOL

7分钟阅读

viewsIcon

217617

downloadIcon

1677

深入了解 .NET 状态机工具包的高级功能。

目录

引言

这是我的 .NET 状态机工具包系列文章的第二部分。在这一部分,我们将探讨其更高级的功能。我们的主要重点将放在创建分层状态机上。我们将使用一个运行中的示例来演示如何使用该工具包。

顶部

交通灯状态机

第一部分 中,我们构建了一个代表电灯开关的状态机。在这一部分,我们将构建一个稍微复杂一点的交通灯状态机。交通灯状态机将有一个 On 和 Off 状态,指示其是否正常运行。此外,当它处于 On 状态时,它将有三个子状态:Red、Yellow 和 Green,指示当前亮起的是哪个灯。

在 On 状态下,状态机将周期性地改变亮起的灯(从红色到绿色再到黄色,然后回到红色)。当然,真正的交通灯会更复杂,但这种简化版本足以满足演示目的。

顶部

实现分层状态机

让我们开始编写代码。这是我们的 TrafficLight 状态机类的开头。

using System;
using Sanford.Threading;
using Sanford.StateMachineToolkit;

namespace TrafficLightDemo
{
    public class TrafficLight : ActiveStateMachine
    {
        public enum EventID
        {
            Dispose,
            TurnOn,
            TurnOff,
            TimerElapsed
        }

        public enum StateID
        {
            On,
            Off,
            Red, 
            Yellow,
            Green,
            Disposed
        }

        private State on, off, red, yellow, green, disposed;

        private DelegateScheduler scheduler = new DelegateScheduler();

        public TrafficLight()
        {
            on = new State((int)StateID.On, new EntryHandler(EntryOn));
            off = new State((int)StateID.Off, new EntryHandler(EntryOff));
            red = new State((int)StateID.Red, new EntryHandler(EntryRed));
            yellow = new State((int)StateID.Yellow, 
                     new EntryHandler(EntryYellow));
            green = new State((int)StateID.Green, 
                    new EntryHandler(EntryGreen));
            disposed = new State((int)StateID.Disposed, 
                       new EntryHandler(EntryDisposed));

            on.Substates.Add(red);
            on.Substates.Add(yellow);
            on.Substates.Add(green);

            on.InitialState = red;

            on.HistoryType = HistoryType.Shallow;            

            Initialize(off);
        }

        #region Entry/Exit Methods        

        #endregion

        public override void Dispose()
        {
            #region Guard

            if(IsDisposed)
            {
                return;
            }

            #endregion

            Send((int)EventID.Dispose);            
        }

        private delegate void SendTimerDelegate();

        private void SendTimerEvent()
        {
            Send((int)EventID.TimerElapsed);
        }
    }
}

这几乎是样板代码,与 第一部分 中的 LightSwitch 状态机类非常相似。但也有一些区别。请注意,redyellowgreen State 已添加到 on StateSubstates 属性中。这使得 redyellowgreen State 成为 on State 的子状态,正如预期的那样。

顶部

初始状态

超级状态有一个初始状态。超级状态的初始状态是其子状态之一,并且是当超级状态是状态转换的目标时进入的状态。在实际应用中,这对我们的交通灯状态机意味着,由于 On 状态是 Red、Yellow 和 Green 子状态的超级状态,它需要一个初始状态,而这个初始状态将是其子状态之一。我们将 Red 子状态设为 On 超级状态的初始状态。当首次进入 On 状态时,它将依次进入 Red 状态。实现这一点只需将子状态分配给超级状态的 InitialState 属性即可。在这种情况下,red State 被分配给 on StateInitialState 属性。必须小心,因为如果该 State 尚未成为超级状态的子状态,那么在将其设置为超级状态的初始状态时将抛出异常。

该图像显示了一个分层状态机。子状态嵌套在它们的超级状态内部。用线连接并以箭头结尾的实心圆指向初始状态。

顶部

历史记录类型

请注意 TrafficLight 代码中,onHistoryType 属性已设置为 Shallow。超级状态可以有历史记录类型。历史记录类型决定了当超级状态退出时,它会记住其哪个子状态是活动的。有三种历史记录类型:NoneShallowDeep。当历史记录类型为 None 时,超级状态将不记得哪个子状态是最后活动的,并在进入时始终以其初始状态为目标。

Shallow 历史记录类型表示超级状态将记住向下移动一个级别的活动子状态。超级状态和子状态可以有多个嵌套级别(参见上面的状态图)。Shallow 历史记录类型只向下移动一个级别,此时子状态在重新进入时将以其初始状态为目标。

Deep 历史记录类型表示超级状态将一直记住状态层次结构底部活动的子状态。当进入超级状态时,它会依次进入最后活动的子状态,该子状态又会依次进入其最后活动的子状态,依此类推。在只有一个子状态级别的情况下(如我们的 TrafficLight),ShallowDeep 历史记录类型是等效的。

对于我们的 TrafficLighton StateShallow 历史记录类型意味着,当交通灯关闭再打开时,它将记住当时哪个灯是亮的。所以,如果交通灯关闭时是绿灯亮着,它会记住,当它再次打开时,会亮起绿灯(目标为 Green 状态)。

浅层历史记录用圆圈中的 H 表示。添加一个星号表示 Deep 历史记录。

顶部

过渡

如果您查看 TrafficLight 代码,您会发现尚未添加任何转换。让我们更详细地描述 TrafficLight 的行为。

TrafficLight 处于 Off 状态时,当收到 TurnOn 事件时,它将转换为 On 状态。当 TrafficLight 处于 On 状态时,无论它处于哪个子状态,当收到 TurnOff 事件时,它将转换为 Off 状态。当处于 On 的某个子状态时,状态机将响应 TimerElapsed 事件。当在 On 的某个子状态时收到 TimerElapsed 事件,它将根据当前所在的子状态转换为另一个状态。例如,如果它处于 Red 子状态,它将转换为 Green 子状态。

当进入 On 的某个子状态时,状态机将使用 DelegateScheduler 对象来调度下一个计时器事件。DelegateScheduler 类属于我的 Sanford.Threading 命名空间。它最初是状态机工具包的一部分,但我已将其与 DelegateQueue 类一起移至我的新线程命名空间。DelegateScheduler 类允许您调度委托调用。我们在这里使用它来指示何时应该改变灯光。

我们希望状态机在 Red 和 Green 子状态停留的时间比在 Yellow 子状态长。因此,当进入 Red 或 Green 子状态时,将安排计时器事件持续时间比进入 Yellow 子状态时长。这是为了模仿真正的交通灯的行为。

这是带有 Transitions 的构造函数。

public TrafficLight()
{
    on = new State((int)StateID.On, new EntryHandler(EntryOn));
    off = new State((int)StateID.Off, new EntryHandler(EntryOff));
    red = new State((int)StateID.Red, new EntryHandler(EntryRed));
    yellow = new State((int)StateID.Yellow, new EntryHandler(EntryYellow));
    green = new State((int)StateID.Green, new EntryHandler(EntryGreen));
    disposed = new State((int)StateID.Disposed, 
               new EntryHandler(EntryDisposed));

    on.Substates.Add(red);
    on.Substates.Add(yellow);
    on.Substates.Add(green);

    on.InitialState = red;

    on.HistoryType = HistoryType.Shallow;

    Transition trans = new Transition(off);
    on.Transitions.Add((int)EventID.TurnOff, trans);

    trans = new Transition(on);
    off.Transitions.Add((int)EventID.TurnOn, trans);

    trans = new Transition(green);
    red.Transitions.Add((int)EventID.TimerElapsed, trans);

    trans = new Transition(yellow);
    green.Transitions.Add((int)EventID.TimerElapsed, trans);

    trans = new Transition(red);
    yellow.Transitions.Add((int)EventID.TimerElapsed, trans);

    trans = new Transition(disposed);
    off.Transitions.Add((int)EventID.Dispose, trans);
    trans = new Transition(disposed);
    on.Transitions.Add((int)EventID.Dispose, trans);

    Initialize(off);
}

这是状态的入口方法。这些是在其状态被进入时调用的方法。

#region Entry/Exit Methods

private void EntryOn()
{
    scheduler.Start();
}

private void EntryOff()
{
    scheduler.Stop();
    scheduler.Clear();
}

private void EntryRed()
{
    scheduler.Add(1, 5000, new SendTimerDelegate(SendTimerEvent));
}

private void EntryYellow()
{
    scheduler.Add(1, 2000, new SendTimerDelegate(SendTimerEvent));
}

private void EntryGreen()
{
    scheduler.Add(1, 5000, new SendTimerDelegate(SendTimerEvent));
}

private void EntryDisposed()
{
    scheduler.Dispose();

    Dispose(true);
}

#endregion

注意 On 状态的入口方法。它启动了 DelegateScheduler。请记住,On 状态是一个超级状态。当它被进入时,它依次进入其子状态之一。它进入哪个子状态取决于当前的历史记录设置。如果 On 状态是第一次进入,它将进入其初始状态。因此,在调用 On 状态的入口方法之后,状态机将依次调用 On 状态的子状态之一的入口方法。例如,如果接下来进入 Red 状态,它将调用 Red 子状态的入口方法。如果您查看该方法,您会发现当进入 Red 子状态时,它会使用 DelegateScheduler 对象安排一个计时事件。当此计时事件过期时,将调用传递给 DelegateScheduler 的委托。在这里,我们传递了一个代表方法的委托,该方法只是向状态机发送一个事件,告知它计时器事件已到期。另外请注意,在进入 Off 状态时,状态机会停止 DelegateScheduler。在 Off 状态下,没有理由让它运行。外界可以通过监听 TransitionCompleted 事件来了解 TrafficLight 何时改变状态。

(为了公平起见,我 DelegateScheduler 类的这种用法至少部分地受到了我的状态机工具包 第三部分 消息板上 leeloo999帖子 的启发。)

顶部

总结

这是我们的 TrafficLight 状态机的完整代码。

using System;
using Sanford.Threading;
using Sanford.StateMachineToolkit;

namespace TrafficLightDemo
{
    public class TrafficLight : StateMachine
    {
        public enum EventID
        {
            Dispose,
            TurnOn,
            TurnOff,
            TimerElapsed
        }

        public enum StateID
        {
            On,
            Off,
            Red, 
            Yellow,
            Green,
            Disposed
        }

        private State on, off, red, yellow, green, disposed;

        private DelegateScheduler scheduler = new DelegateScheduler();

        public TrafficLight()
        {
            on = new State((int)StateID.On, new EntryHandler(EntryOn));
            off = new State((int)StateID.Off, new EntryHandler(EntryOff));
            red = new State((int)StateID.Red, new EntryHandler(EntryRed));
            yellow = new State((int)StateID.Yellow, 
                     new EntryHandler(EntryYellow));
            green = new State((int)StateID.Green, 
                    new EntryHandler(EntryGreen));
            disposed = new State((int)StateID.Disposed, 
                       new EntryHandler(EntryDisposed));

            on.Substates.Add(red);
            on.Substates.Add(yellow);
            on.Substates.Add(green);

            on.InitialState = red;

            on.HistoryType = HistoryType.Shallow;

            Transition trans = new Transition(off);
            on.Transitions.Add((int)EventID.TurnOff, trans);

            trans = new Transition(on);
            off.Transitions.Add((int)EventID.TurnOn, trans);

            trans = new Transition(green);
            red.Transitions.Add((int)EventID.TimerElapsed, trans);

            trans = new Transition(yellow);
            green.Transitions.Add((int)EventID.TimerElapsed, trans);

            trans = new Transition(red);
            yellow.Transitions.Add((int)EventID.TimerElapsed, trans);

            trans = new Transition(disposed);
            off.Transitions.Add((int)EventID.Dispose, trans);
            trans = new Transition(disposed);
            on.Transitions.Add((int)EventID.Dispose, trans);

            Initialize(off);
        }

        #region Entry/Exit Methods

        private void EntryOn()
        {
            scheduler.Start();
        }

        private void EntryOff()
        {
            scheduler.Stop();
            scheduler.Clear();
        }

        private void EntryRed()
        {
            scheduler.Add(1, 5000, new SendTimerDelegate(SendTimerEvent));
        }

        private void EntryYellow()
        {
            scheduler.Add(1, 2000, new SendTimerDelegate(SendTimerEvent));
        }

        private void EntryGreen()
        {
            scheduler.Add(1, 5000, new SendTimerDelegate(SendTimerEvent));
        }

        private void EntryDisposed()
        {
            scheduler.Dispose();

            Dispose(true);
        }

        #endregion

        public override void Dispose()
        {
            #region Guard

            if(IsDisposed)
            {
                return;
            }

            #endregion

            Send((int)EventID.Dispose);            
        }

        private delegate void SendTimerDelegate();

        private void SendTimerEvent()
        {
            Send((int)EventID.TimerElapsed);
        }
    }
}

顶部

依赖项

请务必阅读 第一部分 中的 依赖项 部分。

顶部

结论

在这篇文章中,我们更详细地介绍了 .NET 状态机工具包的高级功能。希望您觉得它有趣且有用。在 第三部分 中,我们将探讨使用代码生成与该工具包。

再次感谢您的时间。一如既往,欢迎评论和建议。

顶部

历史

  • 2005 年 9 月 18 日
    • 首次版本完成。
  • 2005 年 9 月 21 日
    • 结论进行了小幅修改。
  • 2005 年 10 月 6 日
    • 对文章进行了小幅修改;更新了源代码和演示项目。
  • 2005 年 10 月 25 日
    • 对文章进行了重大修订;更新了源代码和演示项目。
  • 2006 年 3 月 23 日
    • 对文章进行了重大修订。
  • 2006 年 5 月 15 日
    • 对文章进行了重大修订。
  • 2006 年 10 月 21 日
    • 对文章进行了小幅修改;更新了源代码和演示项目。

顶部

© . All rights reserved.