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
将在状态{button, Digit}
处发生open
事件。将发生错误。但是如果我在打开函数后添加这些代码:
open(_Event, State) ->
{next_state, open, State}.
然后,如果我在30秒内按下按钮,则不会发生超时。门将永远打开。我该怎么办?
感谢。
我知道我可以使用send_event_after
或类似的东西。但我认为这不是一个好主意。因为您在处理消息时除外的状态可能会在复杂的应用程序中更改。
例如,如果我有一个功能是在门打开30秒后手动锁门。然后locked
将处理timeout
消息,这不是例外行为。
答案 0 :(得分: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).
答案 1 :(得分:0)
您应该在open函数“open(_Event,State)”
中指定超时由于下一个状态在没有超时的情况下继续进行..门将永远保持打开状态,并且不会发生超时..
新定义的功能应该是
open(_Event,State) - &gt; {next_state,open,State,30000}。 %% State应该重新初始化
答案 2 :(得分: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。