Erlang receive子句可以在gen_服务器进程中使用吗?

Erlang receive子句可以在gen_服务器进程中使用吗?,erlang,otp,gen-server,Erlang,Otp,Gen Server,在gen_服务器进程中使用receive子句可以吗?我正在阅读《可伸缩性设计》第10章,其中说: 有什么理由让作者这么说吗?我知道如果我们想与gen_服务器通信,我们应该使用gen_服务器:call/cast,但是如果在handle_call/cast部分中,我们需要receive子句的能力,该怎么办?可以使用它吗?默认情况下,gen_服务器调用和gen_fsm的超时时间为5秒。如果回调函数持续时间过长,则服务器会崩溃,原因如下 {超时,{gen_server,call,[GenServerPi

在gen_服务器进程中使用receive子句可以吗?我正在阅读《可伸缩性设计》第10章,其中说:


有什么理由让作者这么说吗?我知道如果我们想与gen_服务器通信,我们应该使用gen_服务器:call/cast,但是如果在handle_call/cast部分中,我们需要receive子句的能力,该怎么办?可以使用它吗?

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

{超时,{gen_server,call,[GenServerPid,LastMessage]}

我猜cast函数似乎没有相同的超时,因为回调会立即返回,但是如果回调执行阻塞了下一个调用,它将导致下一个调用失败

<> P>所以我认为这不是个好主意除非你的应用程序要求在回调期间短时间内到达一个消息,换句话说,如果你认为第二个消息的缺失是一个错误条件

检查以下代码:

-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处于状态_1,在请求转到状态_2的消息到达之前,收到一条应在状态_2中处理的消息,然后:

必须在状态_1下处理第一条消息,否则进程崩溃, 此消息已从邮箱中删除,并且在fsm切换到state2时不在此处,因此应用程序必须管理此风险,例如,通过单个进程按正确顺序发送这两条消息来避免这种情况。 这种情况无法通过其中一个回调中的接收块解决,因为当进程执行gen_fsm代码时,此问题出现在两次回调之间


我认为这是新的行为gen_statem应该解决的问题之一。R19中的gen_statem我读得很快,但默认情况下gen_服务器调用和gen_fsm的超时时间为5秒。如果回调函数持续时间过长,则服务器会崩溃,原因如下

{超时,{gen_server,call,[GenServerPid,LastMessage]}

我猜cast函数似乎没有相同的超时,因为回调会立即返回,但是如果回调执行阻塞了下一个调用,它将导致下一个调用失败

<> P>所以我认为这不是个好主意除非你的应用程序要求在回调期间短时间内到达一个消息,换句话说,如果你认为第二个消息的缺失是一个错误条件

检查以下代码:

-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处于状态_1,在请求转到状态_2的消息到达之前,收到一条应在状态_2中处理的消息,然后:

必须在状态_1下处理第一条消息,否则进程崩溃, 此消息已从邮箱中删除,并且在fsm切换到state2时不在此处,因此应用程序必须管理此风险,例如,通过单个进程按正确顺序发送这两条消息来避免这种情况。 这种情况无法通过其中一个回调中的接收块解决,因为当进程执行gen_fsm代码时,此问题出现在两次回调之间

我认为这是新的行为方式gen_State应该解决的问题之一。我在R19上看到它时读得很快,不过告诉你为什么使用receive in handle_调用和其他回调可能是个好主意,也可能不是个好主意。我只想指出,作者对这句话的意思似乎并非如此。需要指出的一点是,如果您希望收到两条消息A和B,并且您不确定哪条消息将首先到达,但您希望先处理A,然后再处理B,那么您可以使用receive轻松做到这一点:

但是,如果您的进程是gen_服务器或gen_fsm,则不能执行类似的操作:您的回调函数将按顺序调用传入的消息,如果您想推迟消息B以便稍后处理,则必须将其保存在您的状态或其他状态

在使用HeleLayLault/HeleLaySCAPE时要考虑的另一件事是,您可能会收到,理论上您应该准备好处理。

告诉您为什么使用HeldIn调用和其他回调可能是或不是一个好主意。我只想指出,作者对这句话的意思似乎并非如此。需要指出的一点是,如果您希望收到两条消息A和B,并且您不确定哪条消息将首先到达,但您希望先处理A,然后再处理B,那么您可以使用receive轻松做到这一点:

但是,如果您的进程是gen_服务器或gen_fsm,则不能执行类似的操作:您的回调函数将按顺序调用传入消息,如果您想推迟消息B以便稍后处理,则必须将其保存在您的状态 或者别的什么


在使用HeleLayLault/HeleLayCube时使用的另一个问题是,您可能会接收到,理论上您应该准备好处理。

在我的情况下,没有调用GENA服务器,所有的工作都是通过CAST完成的。我猜你关于超时的说法是对的,但我不认为这是作者这么说的原因。在我的情况下,没有呼叫gen_服务器,所有工作都是通过cast完成的。我猜你关于超时的说法是对的,但我不认为这是作者这么说的原因。再读一遍这句话,我认为你是对的,作者说的不是receive,他说的是handle_call而不是receive。至于系统消息,我认为我不需要担心它们——只要我不在receive子句中匹配它们,它们就会留在邮箱中等待其他组件处理。我是这本书的合著者之一,@legoscia的解释就是我们的意思。无论如何,@Pascal和@legossia在这里都提供了很好的建议。不过,我向OP建议,与其问你是否可以在标准行为中使用receive,不如用一个单独的问题来解释你实际上想要完成的是什么,这样也许我们可以提供可行的替代方案。再读一遍这句话,我认为你是对的,作者不是在说receive,他说的是不使用receive来处理电话。至于系统消息,我认为我不需要担心它们——只要我不在receive子句中匹配它们,它们就会留在邮箱中等待其他组件处理。我是这本书的合著者之一,@legoscia的解释就是我们的意思。无论如何,@Pascal和@legossia在这里都提供了很好的建议。不过,我向OP建议,与其问你是否可以在标准行为中使用receive,不如用一个单独的问题来解释你真正想要实现的是什么,这样也许我们可以提供可行的替代方案。