可以在gen_server进程中使用receive子句吗?

时间:2016-09-07 05:23:29

标签: erlang otp gen-server

在gen_server进程中使用receive子句是否可以?我正在阅读设计可伸缩性的第10章,它说:

enter image description here

是否有任何理由让作者这么说?我知道如果我们想要与gen_server进行通信,我们应该gen_server:call/cast,但是如果在handle_call/cast部分,我们需要receive子句的强大功能呢?可以使用吗?

2 个答案:

答案 0 :(得分:3)

Pascal's answer告诉您为什么在receive中使用handle_call和其他回调可能会或可能不是一个好主意。我只想指出,这似乎不是作者对该句的意思。要点是,如果您期望两条消息A和B,并且您不确定哪一条消息将首先到达,但您想在B之前处理A,则可以使用receive轻松执行此操作:< / p>

wait_for_a() ->
    receive
        {a, A} ->
            process_a(A),
            wait_for_b()
    end.

wait_for_b() ->
    receive
        {b, B} ->
            process_b(B)
    end.

但是,如果你的进程是gen_server或gen_fsm,你不能做那样的事情:你的回调函数将按顺序调用传入的消息,如果你想推迟消息B以便以后处理,你必须将它保存在您的州或其他地方。

receive / handle_call中使用handle_cast时要考虑的另一件事是你可能会收到system messages,理论上你应该准备好处理它。

答案 1 :(得分:2)

默认情况下,gen_server调用(和gen_fsm)的超时时间为5秒。如果回调函数持续时间过长,则服务器崩溃并显示原因

{timeout,{gen_server,call,[GenServerPid,LastMessage]}}

似乎没有强制转换函数具有相同的超时时间(我猜因为回调会立即返回),但如果回调执行阻止它,则会导致下一次调用失败。

所以我认为这不是一个好主意,除非您的应用程序要求在回调期间很快收到消息(换句话说,如果您认为缺少第二条消息是错误条件)

检查以下代码:

-module (tout).

-behaviour(gen_server).

-define(SERVER, ?MODULE).

%% export interfaces
-export([start_link/0,call/2,cast/2]).

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

%% INTERFACES %%

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

call(Pid,Time) ->
    gen_server:call(Pid,{wait,Time}).

cast(Pid,Time) ->
    gen_server:cast(Pid,{wait,Time}).

%% CALLBACK FUNCTIONS %%

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

handle_call({wait,Time}, _From, State) ->
    timer:sleep(Time),
    {reply, done, State};
handle_call(_Request, _From, State) ->
    {reply, {error, unknown_call}, State}.

handle_cast({wait,Time}, State) ->
    timer:sleep(Time),
    {noreply, State};
handle_cast(_Msg, State) ->
    {noreply, State}.

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

terminate(_Reason, _State) ->
    ok.

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

%% LOCAL FUNCTIONS %%

Shell会话:

1> c(tout).                     
{ok,tout}
2> tout:start_link().           
{ok,<0.136.0>}
3> tout:call(tout,500).         
done
4> tout:call(tout,5100).        
** exception exit: {timeout,{gen_server,call,[tout,{wait,5100}]}}
     in function  gen_server:call/2 (gen_server.erl, line 204)
5> tout:start_link().   
{ok,<0.141.0>}
6> tout:cast(tout,10000).
ok
7> tout:cast(tout,1000). 
ok
8> % wait a little .
8> tout:call(tout,100).  
done
9> tout:cast(tout,10000). 
ok
10> % no wait .
11> tout:call(tout,100).  
** exception exit: {timeout,{gen_server,call,[tout,{wait,100}]}}
     in function  gen_server:call/2 (gen_server.erl, line 204)
12> 

[编辑] 是的gen_fsm无法选择性接收,通常的问题是:

Fsm处于state_1并收到一条应该在state_2中处理的消息,就在要求转到state_2的消息到达之前:

  • 必须在state_1中处理此第一条消息,否则进程崩溃,
  • 此消息将从邮箱中删除,并且当fsm将切换到state2时将不在此处,因此应用程序必须管理此风险(例如,通过一个进程以正确的顺序发送2条消息来避免这种情况)

这种情况无法通过其中一个回调中的接收块解决,因为此问题出现在2个回调之间,而进程正在执行gen_fsm代码。

我认为应该通过R19中的新行为gen_statem解决这个问题之一(尽管我读得很快)