在线文档教程

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_handlerinit预计将返回{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_handlerterminate是相反的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}