打印

状态机设计的事件处理疑问

[复制链接]
5397|30
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
xuhongchen|  楼主 | 2008-1-17 08:36 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在状态机的设计中,其实是基于事件触发的机制,即当在某一个状态下发生了某个触发事件,则会转换为另一个状态。
但是如果A状态发生a事件转换为B状态,而B状态发生b事件会装换为C状态,但在A状态如果发生了b事件则仍然维持A状态,但之后由于b事件已经发生了,是否就意味着状态永远不会变到C状态了。
我想是否对事件的搜索应该以状态来划分,在A状态只搜索a事件,B状态只搜索b事件,而不能不管状态来搜索所有的事件?

相关帖子

沙发
dld2| | 2008-1-17 09:15 | 只看该作者

没太明白

首先状态怎么切换是你设计出来的。
在一个状态上,处理所有可能事件,是提高程序安全和完整性的做法。一般来说事件是随机的和不可控的,怎么能在一种状态只处理一种事件呢?

使用特权

评论回复
板凳
armecos| | 2008-1-17 09:18 | 只看该作者

你可以根据实际需要自己设计状态迁移图啊,

如果你希望发生B时间时能转移到C状态,那就在A状态时同时判断A事件和B事件,那么就可以从A态到B态再到C态了。一切由你自己决定。

使用特权

评论回复
地板
xuhongchen|  楼主 | 2008-1-17 10:29 | 只看该作者

想法

如果在B状态需要等待b事件来转换到C状态,但是在前面A状态时b事件已经发生了,所以我想是不是在B状态要重新搜索与B状态有关的所有事件呢?

使用特权

评论回复
5
dld2| | 2008-1-17 14:01 | 只看该作者

有意思的问题

我好象理解楼主的意思了。
例如A状态是吃饭,B状态是上网,C状态是打球。
在吃饭时,如果饭吃完,我会去上网。
在上网时,如果有人叫我去打球,我会去打球。
但是在吃饭时有人让我去打球呢?要求的处理方式是,吃完饭,然后去打球。

我想可以这样考虑:
吃饭时,收到“打球请求”事件,不理它,让这个事件保持在那里。
吃完饭,仍然切换到上网状态。此时检查到“打球请求”事件,变成打球状态。

从实现来说:不同事件可以用一个整型变量的不同bit来表示。
例如“打球请求”是bit0;“吃饭完成”是bit1。发生事件就相当于这些bit置位。事件被处理后应清除相应bit。
在吃饭时,不处理bit0,只处理bit1。
然后在上网时会处理bit1.

我想这个问题只要是事件如何表示的问题。另一个思路是吃饭时发生的“打球请求”事件可以缓冲起来。

使用特权

评论回复
6
xuhongchen|  楼主 | 2008-1-17 14:56 | 只看该作者

继续讨论

呵呵,看来的确是没有说清楚。
事件总是有先后的,当发生了一个事件后将其放入消息队列,而这个事件搜索的进程一般是独立的,与当前的状态无关。
但该消息在某个状态被读走后,其实已经不在队列中了,而对该状态来说此消息是一个非法的消息。
事件一般只产生一次,关键问题是在基于状态机的设计中,事件到底该如何搜索,对于本次认为无效的消息再次放入消息队列的末端,是否会造成消息队列的臃肿并造成不可预知的错误呢?

使用特权

评论回复
7
dld2| | 2008-1-17 15:35 | 只看该作者

好象变成了经典问题:事件和消息的区别

先做个记号,回头再说。

使用特权

评论回复
8
LastNew| | 2008-1-17 15:51 | 只看该作者

这个事件是不是发生得太快了...

这个事件是不是发生得太快了...

或者消息里面是不是应该包括状态信息?

使用特权

评论回复
9
xwj| | 2008-1-17 16:01 | 只看该作者

呵呵,其实就是多作点判断,给你一个模型:

这是用在传输极不可靠场合的一段程序,4个字节的引导头,引导头可以丢1字节(就如同你那个漏掉a事件的例子)

LEAD1发2次,在双向应答和检验+重发机制下可以做到非常的可靠

    if (RI)  
    {
        RI = 0;
        sbufbuf=SBUF;      //接收SBUF缓存
        switch(inlast) 
        {
            case 0:
            {
                if(sbufbuf==LEAD1)          //引导字符1
                    inlast=1;
                else if (sbufbuf==LEAD2)    //引导字符2
                    inlast=2;
                else
                    goto Serial_Error;      //错误       
            }
            break;
            case 1:
            {
                if(sbufbuf==LEAD1)          //引导字符1
                    inlast=1;
                else if(sbufbuf==LEAD2)     //引导字符2
                    inlast=2;
                else if (sbufbuf==LEAD3)    //引导字符3
                    inlast=3;
                else
                    goto Serial_Error;      //错误       
            }
            break;
            case 2:
            {
                if(sbufbuf==LEAD3)          //引导字符3
                    inlast=3;
                else if (sbufbuf==LEAD4)    //引导字符4
                    inlast=4;
                else
                    goto Serial_Error;      //错误       
            }
            break;
            case 3:
            {
                if(sbufbuf==LEAD4)          //引导字符4
                    inlast=4;
                else
                    goto Serial_Error;      //错误       
            }
            break;
Serial_Error:
            case ILEN+3:                    //缓冲溢出
            {
                inlast=0;
            }
            break;
            default:        //4~ILEN+4
            {
                inbuf[inlast-4]=sbufbuf;
                inlast++;
                if(sbufbuf==END)            //接收到结束符
                {
                    inbufsign=1; 
                }
            }
            break;
        }
    }

使用特权

评论回复
10
农民讲习所| | 2008-1-17 16:39 | 只看该作者

俺记得小X是坚决反对goto的

难得看到goto Serial_Error

使用特权

评论回复
11
农民讲习所| | 2008-1-17 16:42 | 只看该作者

事情变化的过程是状态,结果是事件

处理结果(事件)的手段:直接处理,或消息缓冲排队(可转为事件驱动)

使用特权

评论回复
12
dld2| | 2008-1-17 17:05 | 只看该作者

讨论抽象一点的问题

事件是描述发生了什么。是我们实际要处理的东西。而消息应该是从事件派生出来的。
事件一般包括:用户操作事件,外部接口事件,时间事件。
在程序实现中,事件一般由驱动程序去接收;然后可以转化成消息,通过消息队列传送给任务。
现在的问题是:一个任务收到了一个消息,但是在当前状态却不应该处理这个消息,而要延迟到下一个状态才能处理。
但是消息队列机制在应对这样的情况时,显现出了不足。因为它会把没有处理的事件弄丢了。

考虑一个解决方法:
A状态收到了一个有待以后处理的消息,把它放到另外一个消息队列,可称为“遗留问题队列”。
当从A状态切换到B状态时,首先把“遗留问题队列”中的问题处理干净,再去等待“当前问题队列”。

在裸奔时,这个问题反而好处理。因为可以有多个事件源,任务不是阻塞在事件源上,而是在查询各个事件源,并且手工清除事件;未被处理的事件不会丢失。

使用特权

评论回复
13
xuhongchen|  楼主 | 2008-1-17 17:56 | 只看该作者

讨论

感觉状态机这里有一些矛盾,比如在处理按键操作时,可以认为按键是一个事件,在某个状态下该按键无效,那么该按键事件是可以丢弃的。
而在一些控制系统中,由于无法判断某个事件可以丢弃,采用楼上的方法将该事件放入“遗留问题队列”,但是如果该事件是一个可能是X状态发生变化的一个事件,那么这个事件会一直保留到X状态为止有效,好像又不是我们所期望的。
反过来是不是可以说,如果某个事件是离当前状态很远的某个状态的转换条件,说明状态机模型不合理,需要重新划分?

使用特权

评论回复
14
bird777| | 2008-1-17 22:44 | 只看该作者

^_^

12楼 "一个任务收到了一个消息,但是在当前状态却不应该处理这个消息,而要延迟到下一个状态才能处理。但是消息队列机制在应对这样的情况时,显现出了不足。因为它会把没有处理的事件弄丢了"

怎么可能出现这种情况!!!!
一个任务收到了一个消息 == 俺的信箱里有了一封寄给俺的信.
1. 俺不打开看看??
2. 俺打开一看是是要钱的,俺现在没有钱,大概2天后就有钱了,俺就给俺自己写了一封信,2天后收到,这样做不可以吗?
(以上是农民嵌入式OS的农民做法,实在)

 
-------------------------------------------------------------------
 要加油,总的先买车子,买车子,要先赚钱,要赚钱,就要劳动.

没有赚到钱,就没有车子, 怎么会产生加油事件呢???
哦,是帮别人加油,难怪了..........几十年后,终于自己给自己的车加油了,
因为状态才搞清楚了.



使用特权

评论回复
15
农民讲习所| | 2008-1-17 23:16 | 只看该作者

以按键动作为例

按键未按下  --------------状态: 按键IDLE
按键按下动作,还未稳定 ---状态变化中,要判断是否抖动,返回上个状态或进入下个状态
进入下个状态前,产生事件:按键按下,可此时发出消息(WINDOW就这样),或直接调用函数处理(非消息)
按键按下保持中 -----------状态:按键按下保持
按键松开动作,还未稳定 ---状态变化中,要判断是否抖动,返回上个状态或进入下个状态
进入下个状态前,产生事件:按键松开,可此时发出消息
按键松开 == 按键未按下, 回到最开始处。

稳定状态是不需要处理,也不产生事件,从一个稳定状态到另一个稳定状态,产生一个事件,这个事件处理可以使用消息方式。

如此可以表达状态、事件、消息的关系。

使用特权

评论回复
16
IceAge| | 2008-1-18 02:06 | 只看该作者

这是 事件 的时效性

a 事件 仅在 A 状态有效,作为状态转移的触发条件
b 事件 在 B 状态有效, 让 b 在 A 也有效,并不是问题

使用特权

评论回复
17
xuhongchen|  楼主 | 2008-1-18 08:27 | 只看该作者

讨论

我觉得关键问题就是楼上所说的事件的实效性。我上传了一个简单的例子,可以看到5个事件和4个状态,如果在A状态产生了d事件如何处理?你即无法预判也不可能将其丢弃,除非你强制限定该事件只有在C或者D状态才会发生

使用特权

评论回复
18
农民讲习所| | 2008-1-18 09:20 | 只看该作者

根据你的设计要求,判断是否使用事件

事件a,b,c,d,e是状态变化才产生的事件,拿前面那个KEY例子说:
a==按下抖动事件
b==按下有效事件
c==松开抖动事件
d==松开有效事件
这些事件你都可以存放进消息队列,等待任务处理,这些事件没有被系统丢弃,只可能因为任务不需要这些事件,对它不做处理。
这些事件在消息队列中按先后顺序排着队

使用特权

评论回复
19
xwj| | 2008-1-18 09:23 | 只看该作者

觉得LZ对状态机的理解有误

建议你先画出状态迁移图,
你就会知道该怎么做了

使用特权

评论回复
20
sz_kd| | 2008-1-18 09:36 | 只看该作者

晕,有必要搞得那么复杂不

响应事件你可以设定事件的优先级啊

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

7

主题

39

帖子

0

粉丝