有没有办法在Erlang中对gen_server进行异步调用?

时间:2015-02-17 19:52:53

标签: erlang otp

E.g。假设我有一个实现gen_server行为的模块,它有

handle_call({foo, Foo}, _From, State) ->
  {reply, result(Foo), State}
;

我可以通过从其他进程执行gen_server:call(Server,{foo,Foo})来达到此处理程序(我猜如果gen_server尝试gen_server:调用自身,它将死锁)。但是gen_server:响应(或超时)时调用块。如果我不想阻止回复怎么办?

虚构的用例:假设我有5个这样的gen_servers,其中任何2个的响应对我来说已经足够了。我想做的是这样的事情:

OnResponse -> fun(Response) ->
  % blah
end,
lists:foreach(
  fun(S) ->
    gen_server:async_call(S, {foo, Foo}, OnResponse)
  end, 
  Servers),
Result = wait_for_two_responses(Timeout),
lol_i_dunno()

我知道gen_server已经施放,但是施法者无法提供任何回应,所以我认为这不是我想要的。另外,似乎gen_server不应该关注调用者是否想要同步处理响应(使用gen_server:call)或async(似乎不存在?)。

此外,允许服务器通过handle_call返回no_reply并稍后调用gen_server:reply来异步提供响应。那么为什么不在另一端异步支持处理响应呢?或者这是否存在,但我只是没有找到它?

4 个答案:

答案 0 :(得分:2)

gen_server:call基本上是

的序列
send a message to the server (with identifier)
wait for the response of that particular message

包裹在一个功能中。

对于您的示例,您可以通过两个步骤分解行为:对所有服务器使用gen_server:cast(Server,{Message,UniqueID,self()}的循环,然后是等待{UniqueID,Answer}形式的至少2个答案的接收循环。但是您必须在某个时间点清空邮箱。一个更好的解决方案应该是将它委托给一个单独的过程,当它收到所需数量的答案时就会死掉:

[edit] 现在应该在代码中进行一些修正:o)

get_n_answers(Msg,ServerList,N) when N =< length(ServerList) ->
    spawn(?MODULE,get_n_answers,[Msg,ServerList,N,[],self()]).

get_n_answers(_Msg,[],0,Rep,Pid) -> 
    Pid ! {Pid,Rep};
get_n_answers(_Msg,[],N,Rep,Pid) -> 
    NewRep = receive
        Answ -> [Answ|Rep]
    end,
    get_n_answers(_Msg,[],N-1,NewRep,Pid);
get_n_answers(Msg,[H|T],N,Rep,Pid) -> 
    %gen_server:cast(H,{Msg,Pid}),
    H ! {Msg,self()},
    get_n_answers(Msg,T,N,Rep,Pid).

你可以这样使用它:

ID = get_n_answers(Msg,ServerList,2),
% insert some code here
Answer = receive
    {ID,A} -> A % tagged with ID to do not catch another message in the mailbox
end

答案 1 :(得分:1)

你可以通过在一个单独的进程中发送每个调用并等待所需数量的响应来轻松实现这一点(实质上这就是async的意思,不是&#39; t?: - )

查看基于this simple implementation of parallel callasync_call from rpc library in OTP

这是如何用简单的英语。

  • 您需要进行5次调用(在父进程中),您将生成5个子Erlang进程。
  • 每个进程都会向父进程发送一个包含其PID和调用结果的元组。
  • 只有在完成所需的通话后,才能构建元组并发回。
  • 在父进程中,您在接收循环中循环响应。
  • 您可以等待所有回复,或只等待2或3个已启动的回复。

父进程(产生工作进程)最终将收到所有响应(我的意思是你想忽略的那些)。如果您不希望消息队列无限增长,则需要一种方法来丢弃它们。有两种选择:

  1. 父进程本身可以是一个临时进程,仅为产生其他5个子进程的调用创建。一旦收集到所需数量的响应,它就可以将响应发送回呼叫者并死亡。发送到死亡进程的邮件将被丢弃。

  2. 父进程可以在收到所需数量的响应后继续接收消息,然后将其丢弃。

答案 2 :(得分:0)

gen_server在客户端没有异步调用的概念。如何一致地实现并不是一件容易的事,因为gen_server:call是服务器进程的监视器,发送请求消息并等待应答或监视器关闭或超时的组合。如果你做了类似于你提到的事情,你将需要以某种方式处理来自服务器的DOWN消息...所以假设的async_call应该为yeld返回一些密钥,并且还要为你处理DONW消息的情况提供一个内部监视器参考其他进程......并且不希望将其与yeld错误混合在一起。

不是那么好但可能的选择是使用rpc:async_call(gen_server, call, [....])

但是这种方法在调用进程时会有一个限制,它将是一个短暂的rex子进程,所以如果你的gen服务器以某种方式使用调用者pid而不是发送它,那么回复逻辑就会被破坏。

答案 3 :(得分:0)

对进程本身的

gen_sever:call肯定会阻塞直到超时。为了理解原因,我们应该意识到gen_server框架实际上将您的特定代码组合到一个单独的模块中,而gen_server:call将被“翻译”为“pid!Msg”形式。< / p>

想象一下这个代码块是如何生效的,进程实际上保持在循环中保持接收消息,当控制流进入处理函数时,接收进程暂时中断,所以如果你调用{{1}由于它是一个同步函数,它等待响应,但是在处理函数返回之前永远不会进入响应,因此进程可以继续接收消息,因此代码处于死锁状态。