我将我的大部分应用移植到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请求,有时是多次发送,有时一次发送一次,间隔时间间隔。无论如何,我需要按顺序收集它们。
答案 0 :(得分:7)
“plain_fsm”将允许您在仍然符合OTP的情况下进行选择性接收。
答案 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方式。
您仍然可以由主管管理您的服务,并提供所有优惠。