处理非法事件而不清除 gen_fsm 行为中的超时
Posted
技术标签:
【中文标题】处理非法事件而不清除 gen_fsm 行为中的超时【英文标题】:handle illegality events while not clearing timeout in gen_fsm behaviour 【发布时间】:2013-10-30 17:58:11 【问题描述】:Elrang Otp System Documentation 中有一个关于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
将在状态open
出现button, Digit
事件。将发生错误。但是如果我在打开函数之后添加这些代码:
open(_Event, State) ->
next_state, open, State.
然后如果我在 30 秒内按下按钮,将不会发生超时。门将永远打开。我该怎么办?
谢谢。
更新:
我知道我可以使用send_event_after
或类似的东西。但我不认为这是一个好主意。因为在复杂的应用程序中,您处理消息时例外的状态可能会发生变化。
例如,如果我有一个功能,可以在门打开 30 秒后手动锁门。然后locked
将处理timeout
消息,这不是例外行为。
【问题讨论】:
我在您更新后编辑了我的答案。 【参考方案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).
【讨论】:
【参考方案2】:您应该在打开函数“open(_Event, State)”中指定超时
由于没有超时就进入下一个状态..门将永远保持打开状态,并且不会发生超时..
新定义的函数应该是
打开(_Event,状态)-> next_state,打开,状态,30000。 %% 状态应该重新初始化
【讨论】:
但这不会将超时重置为 30 秒吗?如果我想让门在打开后 30 秒后锁上,不管按钮是否按下? 是的.. 这会.. 但如果你想在 30 秒后强制,那么你必须使用带有“erlang:send_after”的单独函数,以便在打开状态下触发锁定。【参考方案3】:使用 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。
【讨论】:
以上是关于处理非法事件而不清除 gen_fsm 行为中的超时的主要内容,如果未能解决你的问题,请参考以下文章