gen_fsm
gen_fsm
为有限状态机,在OTP 20.0被gen_statem
取代。
gen_fsm
behavior函数和回调函数之间的关系如下:
gen_fsm module Callback module
-------------- ---------------
gen_fsm:start
gen_fsm:start_link -----> Module:init/1
gen_fsm:stop -----> Module:terminate/3
gen_fsm:send_event -----> Module:StateName/2
gen_fsm:send_all_state_event -----> Module:handle_event/3
gen_fsm:sync_send_event -----> Module:StateName/3
gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4
- -----> Module:handle_info/3
- -----> Module:terminate/3
- -----> Module:code_change/4
方法gen_fsm:send_event(FsmRef, Event)
会给状态机进程异步发送一个事件Event
,状态机会调用回调函数Module:StateName(Event, State)
来处理该事件,其中StateName
为状态机当前的状态。
方法gen_fsm:end_all_state_event(FsmRef, Event)
会给状态机进程异步发送一个事件Event
,状态机会调用回调函数Module:handle_event(Event, StateName, StateData)
来处理该事件,其中StateName
为状态机当前的状态。
二者区别在于回调函数不同。后者可用于在所有状态下对事件的处理都是一样的情况下,这样可以不用为每个状态去写一个分支。
方法gen_fsm:sync_send_event(FsmRef, Event)
或gen_fsm:sync_send_event(FsmRef, Event, TimeOut)
会给状态机进程同步发送一个事件Event
并等待状态机返回结果,如果TimeOut
ms(默认5000ms)内未返回则调用失败。状态机会调用回调函数Module:StateName(Event, From, State)
来处理该事件,并将结果返回给From
进程,其中StateName
为状态机当前的状态。
对于通过gen_fsm:sync_send_all_state_event(FsmRef, Event)
或gen_fsm:sync_send_all_state_event(FsmRef, Event, Timeout)
发送的事件,状态机通过回调函数Module:handle_sync_event(Event, From, StateName, StateData)
返回。
方法gen_fsm:send_event_after(Time, Event)
会在Time
ms后给状态机进程异步发送一个事件Event
,并返回一个可用cancel_timer(Ref)
取消的定时器引用Ref
。状态机会调用回调函数Module:StateName(Event, State)
来处理该事件,其中StateName
为发送事件时状态机的状态。
方法gen_fsm:start_timer(Time, Msg)
会在Time
ms后给状态机进程异步发送一个事件Event
,并返回一个可用cancel_timer(Ref)
取消的定时器引用Ref
。状态机会调用回调函数Module:StateName({timeout, Ref, Msg}, State)
来处理该事件,其中StateName
为发送事件时状态机的状态。
例子
-module(code_lock).
-define(NAME, code_lock).
-behaviour(gen_fsm).
-export([start_link/1, button/1, stop/0]).
-export([
init/1,
locked/2,
open/2,
handle_sync_event/4,
handle_event/3,
handle_info/3,
terminate/3,
code_change/4]).
start_link(Code) ->
gen_fsm:start_link({local, ?NAME}, ?MODULE, Code, []).
button(Digit) ->
gen_fsm:send_event(?NAME, {button, Digit}).
stop() ->
gen_fsm:sync_send_all_state_event(?NAME, stop).
init(Code) ->
do_lock(), Data = #{code => Code, remaining => Code}, {ok, locked, Data}.
locked({button, Digit}, Data0) ->
case analyze_lock(Digit, Data0) of
{open = StateName, Data} ->
{next_state, StateName, Data, 10000};
{StateName, Data} ->
{next_state, StateName, Data}
end.
open(timeout, State) ->
do_lock(),
{next_state, locked, State};
open({button,_}, Data) ->
{next_state, locked, Data}.
handle_sync_event(stop, _From, _StateName, Data) ->
{stop, normal, ok, Data}.
handle_event(Event, StateName, Data) ->
{stop, {shutdown, {unexpected, Event, StateName}}, Data}.
handle_info(Info, StateName, Data) ->
{stop, {shutdown, {unexpected, Info, StateName}}, StateName, Data}.
terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
{ok, State, Data}.
analyze_lock(Digit, #{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] ->
do_unlock(),
{open, Data#{remaining := Code}};
[Digit|Rest] ->
% Incomplete
{locked, Data#{remaining := Rest}};
_Wrong ->
{locked, Data#{remaining := Code}}
end.
do_lock() ->
io:format("Lock~n", []).
do_unlock() ->
io:format("Unlock~n", []).
把gen_fsm改写成gen_statem
%% https://erldocs.com/20.0/stdlib/gen_fsm.html#Migration%20to%20gen_statem
-module(code_lock).
-define(NAME, code_lock).
%-define(BEFORE_REWRITE, true).
-ifdef(BEFORE_REWRITE).
-behaviour(gen_fsm).
-else.
-behaviour(gen_statem).
-endif.
-export([start_link/1, button/1, stop/0]).
-ifdef(BEFORE_REWRITE).
-export([
init/1,
locked/2,
open/2,
handle_sync_event/4,
handle_event/3,
handle_info/3,
terminate/3,
code_change/4]).
-else.
-export([
init/1
, callback_mode/0
, locked/3
, open/3
, terminate/3
, code_change/4]).
%% Add callback__mode/0
%% Change arity of the state functions
%% Remove handle_info/3
-endif.
-ifdef(BEFORE_REWRITE).
start_link(Code) ->
gen_fsm:start_link({local, ?NAME}, ?MODULE, Code, []).
-else.
start_link(Code) ->
gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
-endif.
-ifdef(BEFORE_REWRITE).
button(Digit) ->
gen_fsm:send_event(?NAME, {button, Digit}).
-else.
button(Digit) ->
gen_statem:cast(?NAME, {button,Digit}).
%% send_event is asynchronous and becomes a cast
-endif.
-ifdef(BEFORE_REWRITE).
stop() ->
gen_fsm:sync_send_all_state_event(?NAME, stop).
-else.
stop() ->
gen_statem:call(?NAME, stop).
%% sync_send is synchronous and becomes call
%% all_state is handled by callback code in gen_statem
-endif.
init(Code) ->
do_lock(), Data = #{code => Code, remaining => Code}, {ok, locked, Data}.
-ifdef(BEFORE_REWRITE).
-else.
callback_mode() -> state_functions.
%% state_functions mode is the mode most similar to
%% gen_fsm. There is also handle_event mode which is
%% a fairly different concept.
-endif.
-ifdef(BEFORE_REWRITE).
locked({button, Digit}, Data0) ->
case analyze_lock(Digit, Data0) of
{open = StateName, Data} ->
{next_state, StateName, Data, 10000};
{StateName, Data} ->
{next_state, StateName, Data}
end.
-else.
locked(cast, {button,Digit}, Data0) ->
case analyze_lock(Digit, Data0) of
{open = StateName, Data} ->
{next_state, StateName, Data, 10000};
{StateName, Data} ->
{next_state, StateName, Data}
end;
locked({call, From}, Msg, Data) ->
handle_call(From, Msg, Data);
locked({info, Msg}, StateName, Data) ->
handle_info(Msg, StateName, Data).
%% Arity differs
%% All state events are dispatched to handle_call and handle_info help
%% functions. If you want to handle a call or cast event specifically
%% for this state you would add a special clause for it above.
-endif.
-ifdef(BEFORE_REWRITE).
open(timeout, State) ->
do_lock(),
{next_state, locked, State};
open({button,_}, Data) ->
{next_state, locked, Data}.
-else.
open(timeout, _, Data) ->
do_lock(),
{next_state, locked, Data};
open(cast, {button,_}, Data) ->
{next_state, locked, Data};
open({call, From}, Msg, Data) ->
handle_call(From, Msg, Data);
open(info, Msg, Data) ->
handle_info(Msg, open, Data).
%% Arity differs
%% All state events are dispatched to handle_call and handle_info help
%% functions. If you want to handle a call or cast event specifically
%% for this state you would add a special clause for it above.
-endif.
-ifdef(BEFORE_REWRITE).
handle_sync_event(stop, _From, _StateName, Data) ->
{stop, normal, ok, Data}.
handle_event(Event, StateName, Data) ->
{stop, {shutdown, {unexpected, Event, StateName}}, Data}.
handle_info(Info, StateName, Data) ->
{stop, {shutdown, {unexpected, Info, StateName}}, StateName, Data}.
-else.
-endif.
terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
{ok, State, Data}.
%% Internal functions
-ifdef(BEFORE_REWRITE).
-else.
handle_call(From, stop, Data) ->
{stop_and_reply, normal, {reply, From, ok}, Data}.
handle_info(Info, StateName, Data) ->
{stop, {shutdown, {unexpected, Info, StateName}}, StateName, Data}.
%% These are internal functions for handling all state events
%% and not behaviour callbacks as in gen_fsm
-endif.
analyze_lock(Digit, #{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] ->
do_unlock(),
{open, Data#{remaining := Code}};
[Digit|Rest] ->
% Incomplete
{locked, Data#{remaining := Rest}};
_Wrong ->
{locked, Data#{remaining := Code}}
end.
do_lock() ->
io:format("Lock~n", []).
do_unlock() ->
io:format("Unlock~n", []).