如何在GenServer中执行对当前进程的调用?

时间:2016-02-12 14:19:16

标签: elixir otp

我知道我可以像这样打电话给GenServer

GenServer.call(pid, request)
# using a pid

或者像这样

GenServer.call(registered_name, request)
# if I registered the process with a name

但是有没有办法在不知道pid /注册名称的情况下在模块内部执行GenServer.call?(即有类似GenServer.call(__CURRENT_PROCESS__, request)的内容吗?)

4 个答案:

答案 0 :(得分:5)

这根本行不通。 GenServer.call只是向给定进程发送消息,然后等待另一条消息(回复),阻止当前进程。如果您以这种方式向self发送消息,则该过程将无法自由处理该消息,因为它将被阻止等待回复。因此call将永远超时。​​

我怀疑你需要的只是将你想要的功能提取到一个函数中并直接调用它。或者,您可以向当前流程发送cast,有效地告诉它“稍后”执行某些操作。

答案 1 :(得分:4)

我不确定这是最好的方式,但你要找的是Kernel.self/0

编辑:

Per Sasha的评论代码看起来像这样:

GenServer.call(self, request)

编辑:

根据Pawel Obrok的优点,你不应该打电话给当前的流程。如果您需要向当前流程发送消息,您应该在这个方面进行:

GenServer.cast(self, request) 

注意它是投射与呼叫。

答案 2 :(得分:3)

这取决于。如果您每个节点只启动一个GenServer进程,则可以将其称为:

@doc """
If you want to call the server only from the current module.
"""
def local_call(message) do
  GenServer.call(__MODULE__, message)
end

@doc """
If you want to call the server from another node on the network.
"""
def remote_call(message, server \\ nil) do
  server = if server == nil, do: node(), else: server
  GenServer.call({__MODULE__, server}, message)
end

如果您有来自同一模块的多个流程,则需要一个额外的标识符(例如,如果您有一个主管策略:simple_one_for_one来按需生成GenServer

对于类似的东西我建议使用:

  • :gproc来命名流程。
  • :ets如果您需要额外信息来识别您的流程。

:gproc很棒,适用于GenServer。您通常使用atom作为注册名称来命名您的进程。 :gproc允许您使用任意术语(即元组)命名您的流程。

假设我的函数调用中有一个复杂的服务器标识符,例如{:id, id :: term},其中id可以是一个字符串。我可以像GenServer那样开始:

defmodule MyServer do
  use GenServer

  def start_link(id) do
    name = {:n, :l, {:id, id}}
    GenServer.start_link(__MODULE__, %{}, name: {:via, :gproc, name})
  end
  (...)
end

通过不同于原子的东西查找我的过程,就像我之前说过的那样,例如一个字符串。所以,如果我启动我的服务器,如:

MyServer.start_link("My Random Hash")

我的功能如下:

def f(id, message) do
  improved_call(id, message)
end

defp improved_call(id, message, timeout \\ 5000) do
  name = {:n, :l, {:id, id}}
  case :gproc.where(name) do
    undefined -> :error
    pid -> GenServer.call(pid, message, timeout)
end

您可以使用它来调用以下流程:

MyServer.f("My Random Hash", {:message, "Hello"})

如果您的命名过程更复杂,您可以使用:ets来存储更复杂的状态。

如果您想使用:gproc,可以将其添加到mix.exs文件中,例如:

(...)
defp deps do
  [{:gproc, github: "uwiger/gproc"}]
end
(...)

另一方面,永远不要在GenServer.call/3内拨打handle_call/3。它将超时并在其他GenServer.call上执行DOS。 handle_call/3当时处理一条消息。

答案 3 :(得分:1)

根据我的评论,您正在尝试为GenServer编写公共API函数,而不是在call进程中创建GenServer。如果不了解PID,AFAIK就没有办法做到这一点。但是,对于GenServers,您只创建一个实例,存在这样一种情况的惯用语:您将GenServer的唯一实例命名为实现它的模块的名称。这可以使用__MODULE__宏轻松完成:

def start_link do
  GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end

然后在您的API函数中,您只需使用__MODULE__代替PID:

def api_function do
  GenServer.call(__MODULE__, :api_function)
end

这具有很好的属性,__MODULE__将始终反映封闭模块的正确名称,即使重命名它也是如此。