Обрабатывать события незаконности, не сбрасывая время ожидания в поведении gen_fsm
Пример запертой двери gen_fsm
в системной документации Elrang Otp. У меня есть вопрос по поводу тайм-аута. Сначала я скопирую код здесь:
-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
сообщение, которое не является исключительным поведением.
3 ответа
Вы можете сохранить оставшееся время ожидания в 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).
Используя таймаут 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.
Вам нужно будет обработать Pid процесса тайм-аута в состоянии FSM.
Вы должны указать время ожидания в открытой функции "open(_Event, State)"
Поскольку следующее состояние выполняется без тайм-аута.. дверь останется открытой навсегда, и нигде не произойдет тайм-аут..
Вновь определенная функция должна быть
open(_Event, State) -> {next_state, open, State, 30000}. %% State должно быть повторно инициализировано