1

Elrang Otp 系统文档gen_fsm中有一个锁门示例。我有一个关于超时的问题。我先把代码复制到这里:

-module(code_lock).
-behaviour(gen_fsm).
-export([start_link/1]).
-export([button/1]).
-export([init/1, locked/2, open/2]).

start_link(Code) ->
    gen_fsm:start_link({local, code_lock}, code_lock, lists:reverse(Code), []).

button(Digit) ->
    gen_fsm:send_event(code_lock, {button, Digit}).

init(Code) ->
    {ok, locked, {[], Code}}.

locked({button, Digit}, {SoFar, Code}) ->
    case [Digit|SoFar] of
    Code ->
        do_unlock(),
        {next_state, open, {[], Code}, 30000};
    Incomplete when length(Incomplete)<length(Code) ->
        {next_state, locked, {Incomplete, Code}};
    _Wrong ->
        {next_state, locked, {[], Code}}
    end.

open(timeout, State) ->
    do_lock(),
    {next_state, locked, State}.

这里有一个问题:当门打开时,如果我按下按钮,状态gen_fsm将有一个{button, Digit}事件open。将发生错误。但是如果我在打开函数之后添加这些代码:

open(_Event, State) ->
   {next_state, open, State}.

然后如果我在 30 秒内按下按钮,则不会发生超时。门将永远打开。我应该怎么办?

谢谢。

更新:

我知道我可以使用send_event_after或类似的东西。但我不认为这是一个好主意。因为您处理消息的例外状态可能会在复杂的应用程序中更改。

例如,如果我有一个功能,可以在 30 秒开门后手动锁门。然后locked将处理timeout消息,这不是例外行为。

4

3 回答 3

1

您可以将剩余超时保持在StateData. 为此,向元组添加第三项:

init(Code) ->
    {ok, locked, {[], Code, infinity}}.

您需要更改locked以设置初始值:

locked({button, Digit}, {SoFar, Code, _Until}) ->
    case [Digit|SoFar] of
        Code ->
            do_unlock(),
            Timeout = 30000,
            Now = to_milliseconds(os:timestamp()),
            Until = Now + Timeout,
            {next_state, open, {[], Code, Until}, Timeout};
        Incomplete when length(Incomplete)<length(Code) ->
            {next_state, locked, {Incomplete, Code, infinity}};
        _Wrong ->
            {next_state, locked, {[], Code, infinity}}
    end.

并且,如果在打开时按下按钮,则计算新的超时并再次执行:

open({button, _Digit}, {_SoFar, _Code, Until} = State) ->
    Now = to_milliseconds(os:timestamp()),
    Timeout = Until - Now,
    {next_state, open, State, Timeout};

您还需要以下辅助函数:

to_milliseconds({Me, S, Mu}) ->
    (Me * 1000 * 1000 * 1000) + (S * 1000) + (Mu div 1000).
于 2013-11-12T11:44:53.513 回答
0

您应该在打开函数“open(_Event,State)”中指定超时

由于下一个状态在没有超时的情况下进行..门将永远保持打开状态,并且不会发生超时..

新定义的函数应该是

打开(_Event,状态)-> {next_state,打开,状态,30000}。%% 状态应该重新初始化

于 2013-10-22T05:26:55.463 回答
0

使用 fsm 超时,这是不可能的 - 据我所知 - 避免重新初始化它:

  • 如果您在门打开时跳过事件时未指定新的超时,则它将永远保持打开状态,如您所见。
  • 如果您指定一个,它将从头开始重新启动。

如果这些解决方案都不能让您满意,您可以使用外部进程来创建超时:

-module(code_lock).
-behaviour(gen_fsm).


-export([start_link/1]).
-export([button/1,stop/0]).
-export([init/1, locked/2, open/2,handle_event/3,terminate/3]).

start_link(Code) ->
    gen_fsm:start_link({local, code_lock}, code_lock, lists:reverse(Code), []).

button(Digit) ->
    gen_fsm:send_event(code_lock, {button, Digit}).
stop() ->
    gen_fsm:send_all_state_event(code_lock, stop).


init(Code) ->
    {ok, locked, {[], Code}}.

locked({button, Digit}, {SoFar, Code}) ->
    case [Digit|SoFar] of
    Code ->
        do_unlock(),
        timeout(10000,code_lock),
        {next_state, open, {[], Code}};
    Incomplete when length(Incomplete)<length(Code) ->
        {next_state, locked, {Incomplete, Code}};
    _Wrong ->
        {next_state, locked, {[], Code}}
    end.

open(timeout, State) ->
    do_lock(),
    {next_state, locked, State};
open(_, State) ->
    {next_state, open, State}.

handle_event(stop, _StateName, StateData) ->
    {stop, normal, StateData}.


terminate(normal, _StateName, _StateData) ->
    ok.

do_lock() -> io:format("locking the door~n").
do_unlock() -> io:format("unlocking the door~n").

timeout(X,M) ->
        spawn(fun () -> receive
                        after X -> gen_fsm:send_event(M,timeout)
                        end
                end).

模块计时器中有很多功能可以做到这一点,比我的自定义示例更可取。

也许更好地使用 Fsm 超时应该处于锁定状态:

  • 等待第一个数字而不超时
  • 输入一个数字并且代码完成 - >测试它并继续没有超时(锁定或打开取决于输入的代码)
  • 输入了一个数字但代码不完整-> 存储它并继续超时
  • 如果发生意外事件 -> 从头开始​​重新启动而不会超时
  • 如果超时吠叫,从头开始重新启动,没有超时

编辑:王斌:你在更新中所说的是正确的,但你不能避免管理这种情况。我不知道任何涵盖您的用例的内置函数。为了满足它,您需要在锁定状态下管理意外的超时消息,但是为了避免多次超时运行,您还需要在进入锁定状态之前停止当前的超时消息。请注意,这不会阻止您在锁定状态下管理超时消息,因为停止计时器的消息与超时本身之间存在竞争。我为我的一个应用程序编写了一个通用的 apply_after 函数,可以取消、停止和恢复:

applyAfter_link(T, F, A) ->
    V3 = time_ms(),
    spawn_link(fun () -> applyAfterp(T, F, A, V3) end).

applyAfterp(T, F, A, Time) ->
    receive
        cancel -> ok;
        suspend when T =/= infinity ->
           applyAfterp(infinity, F, A, T + Time - time_ms());
        suspend ->
           applyAfterp(T, F, A, Time);
        resume when T == infinity ->
           applyAfterp(Time, F, A, time_ms());
        resume ->
           Tms = time_ms(), applyAfterp(T + Time - Tms, F, A, Tms)
        after T ->
           %% io:format("apply after time ~p, function ~p, arg ~p , stored time ~p~n",[T,F,A,Time]),
           catch F(A)
    end.

time_us() ->
    {M, S, U} = erlang:now(),
    1000000 * (1000000 * M + S) + U.

time_ms() -> time_us() div 1000.

您将需要在 FSM 状态下处理超时进程的 Pid。

于 2013-10-22T06:09:01.600 回答