你如何在gen_servers中进行选择性接收?

时间:2009-08-17 21:02:06

标签: erlang

我将我的大部分应用移植到OTP行为中,但我被卡住了。我无法弄清楚如何使用gen_server进行选择性接收。如果没有回调函数子句与消息匹配,而不是将消息放回邮箱中,则会出错!

现在,无论我走到哪里,人们都赞不绝口。我去的每个地方,人们都称赞OTP。你真的不能同时拥有这两个吗?这不是一个重大的,可纠正的缺点吗?

erlang程序员如何处理这个问题?

编辑(回应zed的评论):

这是一个例子,我希望看到按排序顺序打印的整数列表:

-module(sel_recv).
-behaviour(gen_server).

-export([start_link/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
     terminate/2, code_change/3]).

-export([test/0]).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

test() ->
    gen_server:cast(?MODULE, test).

init([]) ->
    {ok, 0}.

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

handle_cast(test, _State) ->
    lists:map(fun(N) ->
                      gen_server:cast(?MODULE, {result, N})
              end, [9,8,7,6,5,4,3,2,1]),
    {noreply, [1,2,4,5,6,7,8,9]};
handle_cast({result, N}, [N|R]) ->
    io:format("result: " ++ integer_to_list(N) ++ "~n"),
    {noreply, R}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

当然,在我的真实应用程序中,存在计时器延迟,并且需要按顺序处理的消息与其他消息交错。特别是,我发送了http请求,有时是多次发送,有时一次发送一次,间隔时间间隔。无论如何,我需要按顺序收集它们。

5 个答案:

答案 0 :(得分:7)

“plain_fsm”将允许您在仍然符合OTP的情况下进行选择性接收。

http://github.com/esl/plain_fsm

答案 1 :(得分:4)

Gen_server可能不是最佳选择。 您可以做的一件事是将所有消息接收到缓冲区列表中,并自己实现选择性接收:

handle_cast(test, _State) ->
    ...
    {noreply, {[1,2,4,5,6,7,8,9], []}};

handle_cast({result, N}, {Wait, Buff}) ->
    {noreply, handle_results(Wait, [N|Buff])}.

handle_results([], Buff) ->
    {[], Buff};

handle_results([W|WTail] = Wait, Buff) ->
    case lists:member(W, Buff) of
        true ->
            io:format("result: " ++ integer_to_list(W) ++ "~n"),
            handle_results(WTail, Buff -- [W]);
        false ->
            {Wait, Buff}
    end.

答案 2 :(得分:4)

不,gen_server的设计不能处理选择性接收,每个请求在到达时都会被处理。这实际上是一个难题,因为Erlang要求所有模式在编译时都是已知的,没有“模式对象”。

我同意gen_fsm可能不适合你,因为在你的状态数量爆炸之前它不会以任何顺序到达不同的消息。这是我们添加选择性接收的原因之一,它允许您安全地忽略不感兴趣的消息,将它们留待以后使用。

您对哪个OTP特别感兴趣?

答案 3 :(得分:3)

也许你真的想要使用gen_fsm。这种行为通常是协议的前端选择,协议具有某些状态,需要根据当前状态不同地处理请求。

但回到gen_server,我们使用gen_server作为其功能。它订阅系统事件以进行代码加载,并为您提供code_change回调。它导致sasl报告崩溃。您将获得一个有助于代码维护的众所周知的结构。最重要的是,它为同步调用实现了一个设计良好的协议。

在这种情况下,很难说OTP行为是否适合您。


鉴于评论,听起来像gen_server对你来说是错误的。当我使用gen_server行为时,我认为它是公司的老板。每个人都希望与老板交谈,因此让老板能够快速有效地委派工作符合公司的最大利益,这样他就不会让人等待而不是工作。

gen_server可以使用'From'参数进行委派。为此,请返回{no_reply, State} 并将From参数传递给委托。代表使用gen_server:reply/2来回答原始电话。

这种使用gen_server的方法可能适合你。启动一个“普通”进程,使用接收端进行选择性接收。如果这是一个真正独立的工作,gen_server可以忽略它(发射并忘记)。如果它想知道它是否已完成,可以gen_server:cast/2从启动的“普通”过程中回复它的消息。

如果你想在选择性接收时阻止gen_server,那么也可以在启动过程并等待它返回之前死掉。选择性接收是消息按其到达顺序进行的O(n)线性搜索。因此,每个选择性接收将扫描所有排队的消息,这些消息对于流行的gen_server来说可能很高。

没有公司应该只有老板在那里工作。没有erlang应用程序应该只有gen_servers。

答案 4 :(得分:2)

仅仅因为你不能将gen_server用于你的某个模块并不意味着你没有使用OTP。所有回调模块都为您实现接收块,阻止您使用选择性接收。没有理由你不能实现自己的处理选择性接收的服务。这样做并不意味着你没有采用OTP方式。

您仍然可以由主管管理您的服务,并提供所有优惠。