非常教程

Erlang 20参考手册

指南:设计原则 | Guide: Design principles

4. gen_event Behaviour

本节将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}
Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1