4. gen_event Behaviour
4 gen_event 行为
本节将gen_event(3)
在 STDLIB 中的手册页中进行阅读,其中详细介绍了所有接口函数和回调函数。
4.1 事件处理原则
在 OTP 中,事件管理器
是可以发送事件
的命名对象。一个事件
可以是,例如,错误,警报,或一些信息,这些信息将被记录。
在事件管理器中,安装零个,一个或多个事件处理
程序。当事件管理器收到有关事件的通知时,事件由所有安装的事件处理
程序处理。例如,处理错误的事件管理器默认情况下可以安装一个处理程序,它将错误消息写入终端。如果某段时间内的错误消息也要保存到文件中,则用户将添加另一个执行此操作的事件处理
程序。当不再需要记录到文件时,该事件处理
程序将被删除。
事件管理器是作为一个进程实现的,每个事件处理器都被实现为一个回调模块。
事件管理器实质上维护一个{Module, State}
对列表,其中每个对Module
都是一个事件处理程序,并且State
是该事件处理程序的内部状态。
4.2 示例
事件处理程序向终端写入错误消息的回调模块可能如下所示:
-module(terminal_logger).
-behaviour(gen_event).
-export([init/1, handle_event/2, terminate/2]).
init(_Args) ->
{ok, []}.
handle_event(ErrorMsg, State) ->
io:format("***Error*** ~p~n", [ErrorMsg]),
{ok, State}.
terminate(_Args, _State) ->
ok.
事件处理程序将错误消息写入文件的回调模块可能如下所示:
-module(file_logger).
-behaviour(gen_event).
-export([init/1, handle_event/2, terminate/2]).
init(File) ->
{ok, Fd} = file:open(File, read),
{ok, Fd}.
handle_event(ErrorMsg, Fd) ->
io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
{ok, Fd}.
terminate(_Args, Fd) ->
file:close(Fd).
代码在下一节中解释。
4.3 启动事件管理器
如前例所述,要启动事件管理器来处理错误,请调用以下函数:
gen_event:start_link{local, error_man})
这个函数产生并链接到一个新的进程,一个事件管理器。
参数{local, error_man}
指定了名称。活动经理然后在当地注册为error_man
。
如果名称被省略,则事件管理器未被注册。相反,它的pid必须使用。该名称也可以作为{global, Name}
,在这种情况下,事件管理器使用注册global:register_name/2
。
如果事件管理器是监督树的一部分,即由监督员启动,那么必须使用gen_event:start_link
。还有一个功能gen_event:start
是启动一个独立的事件管理器,即一个不属于监督树的事件管理器。
4.4 添加事件处理程序
以下示例显示了如何启动事件管理器并使用 shell 向其添加事件处理程序:
1> gen_event:start{local, error_man}).
{ok,<0.31.0>}
2> gen_event:add_handler(error_man, terminal_logger, []).
ok
这个函数发送一条消息给注册为的事件管理器error_man
,告诉它添加事件处理器terminal_logger
。事件管理器调用回调函数terminal_logger:init([])
,其中参数[]
是第三个参数add_handler
。init
预计将返回{ok, State}
,State
事件处理程序的内部状态在哪里。
init(_Args) ->
{ok, []}.
在这里,init
不需要任何输入数据并忽略它的参数。因为terminal_logger
,内部状态不被使用。因为file_logger
内部状态用于保存打开的文件描述符。
init(File) ->
{ok, Fd} = file:open(File, read),
{ok, Fd}.
4.5 通知事件
3> gen_event:notify(error_man, no_reply).
***Error*** no_reply
ok
error_man
是事件管理者的名字,no_reply
是活动。
该事件被编制成消息并发送给事件管理者。当收到事件时,事件管理器将handle_event(Event, State)
按照添加的顺序调用每个安装的事件处理程序。该函数应该返回一个元组{ok,State1}
,其中State1
是事件处理程序状态的新值。
在terminal_logger
*
handle_event(ErrorMsg, State) ->
io:format("***Error*** ~p~n", [ErrorMsg]),
{ok, State}.
在file_logger
:
handle_event(ErrorMsg, Fd) ->
io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
{ok, Fd}.
4.6 删除事件处理程序
4> gen_event:delete_handler(error_man, terminal_logger, []).
ok
此函数向注册为的事件管理器发送一条消息error_man
,告诉它删除该事件处理器terminal_logger
。事件管理器调用回调函数terminal_logger:terminate([], State)
,其中参数[]
是第三个参数delete_handler
。terminate
是相反的init
并且做任何必要的清理。其返回值被忽略。
因为terminal_logger
,不需要清理:
terminate(_Args, _State) ->
ok.
因为file_logger
,打开的文件描述符init
必须关闭:
terminate(_Args, Fd) ->
file:close(Fd).
4.7 停止
当事件管理器停止时,它会通过调用来为每个安装的事件处理程序提供清理的机会terminate/2
,就像删除处理程序时一样。
在监督树中
如果事件管理器是监督树的一部分,则不需要停止功能。活动经理由其主管自动终止。具体如何完成是由shutdown strategy
主管人员定义的。
独立事件管理器
一个事件管理器也可以通过调用:
> gen_event:stop(error_man).
ok
4.8 处理其他消息
如果gen_event
能够接收除事件之外的其他消息,则handle_info(Info, StateName, StateData)
必须实施回调函数来处理它们。其他消息的例子是退出消息,如果gen_event
它链接到其他进程(比管理程序)和陷阱退出信号。
handle_info{'EXIT', Pid, Reason}, State) ->
..code to handle exits here..
{ok, NewState}.
code_change
方法也必须实施。
code_change(OldVsn, State, Extra) ->
..code to convert state (and more) during code change
{ok, NewState}