从另一个GenServer调用GenServer

时间:2018-12-06 12:47:01

标签: elixir gen-server

我有一个使用2个GenServer的项目 第一个名为“状态”的GenServer维护状态,第二个名为“更新”的GenServer维护状态的可能更新列表。我想要实现的是:

每次调用“状态”时,“状态”应调用“更新”并在返回实际状态之前进行更新。

两个GenServer都是由主管启动的,我可以从外部按名称调用两个GenServer,但是如果尝试在“状态”内部调用“更新”的API,则“状态”将以“无进程”错误终止。有什么建议吗?

  def start_link(opts) do
    Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def init(_arg) do
    children = [
      {Updates, name: Updates},
      {State, name: State}
    ]

    Supervisor.init(children, strategy: :rest_for_one)
  end

GenServer均以

开头
  def start_link(opts) do
    GenServer.start_link(__MODULE__, [], opts)
  end

在“状态”中,我有回调

  @impl true
  def handle_call({:get}, _from, state) do
    updates = Updates.get_updates(Updates)
    {:reply, updates, state}
  end

再次,例如,如果我直接从iex调用Updates.get_updates(Updates),一切都会按预期运行,因此我认为对我的主管来说一切都很好。似乎“州”不知道“更新”的名称。

Updates.get_updates / 1实现是:

  def get_updates(pid) do
    GenServer.call(pid, :get)
  end

其中回调只是回复状态

  @impl true
  def handle_call(:get, _from, state) do
    {:reply, state, state}
  end

1 个答案:

答案 0 :(得分:1)

  

状态”以“无进程”错误终止。有任何建议吗?

根据Supervisor docschildren列表:

   children = [
      {Updates, name: Updates},
      {State, name: State}
    ]

应该是child specification元组的列表,其中子规范具有以下有效键:

  

子规范包含6个键。前两个是必填项,   其余的是可选的:

     

:id -用于在内部标识子规范的任何术语   主管;默认为给定的模块。如果是   :id值冲突,主管将拒绝初始化和   需要明确的ID。该密钥是必需的。

     

:start -具有要启动的module-function-args的元组   子进程。该密钥是必需的。

     

:重新启动-一个原子,它定义何时终止子进程   重新启动(请参阅下面的“重新启动值”部分)。这个钥匙是   可选,默认为:permanent。

     

:关机-一个原子,它定义子进程的方式   终止(请参见下面的“关闭值”部分)。这个钥匙是   可选,如果类型为:worker或:infinity,则默认为5000   类型是:主管。

     

:类型-指定子进程是:worker还是   :主管该密钥是可选的,默认为:worker。

     

有第六个键,:模块,该键很少更改。设置   自动根据:start中的值。

请注意,没有name:键,您要在子说明中列出该键。

但是,GenServer.start_link()确实有一个name:选项:

  

start_link / 3和start / 3都支持GenServer注册名称   首先通过:name选项。注册名称也自动   在终止时清理。支持的值是:

     

一个原子-GenServer使用Process.register / 2在本地注册了给定名称。

     

{:global,term} -使用:global模块中的功能,以给定的术语对GenServer进行全局注册。

     

{:通过模块,术语} -GenServer已使用给定的机制和名称进行注册。 :via选项需要一个导出的模块   register_name / 2,unregister_name / 1,isis_name / 1和send / 2。一   这样的例子是:global模块,它使用这些功能   保留进程名称及其相关的PID的列表,   可用于Elixir节点的网络。长生不老药也   带有一个本地,分散和可扩展的注册表,称为   用于本地存储动态生成的名称的注册表。

     

例如,我们可以在本地启动并注册我们的Stack服务器为   如下:

# Start the server and register it locally with name:

MyStack {:ok, _} = GenServer.start_link(Stack, [:hello], name: MyStack)

所以,我认为您应该这样做:

  def init(_arg) do
    children = [
      Updates,
      State
    ]

然后在您的GenServer start_link()函数中:

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

======

这里是一个完整的例子。在application.ex中,您可以指定要注册的名称:

children = [
  # Starts a worker by calling: Servers.Worker.start_link(arg)
  # {Servers.Worker, arg},
  {
    Servers.CurrentState, [ 
      init_state_with: [:hello, 10], 
      name_to_register: Servers.CurrentState
    ] 
  },
  {
    Servers.Updates, [
      init_state_with: [:goodbye], 
      name_to_register: Servers.Updates
    ]
  }
]

然后,您可以这样定义两个GenServer:

lib / servers / updates.ex:

defmodule Servers.Updates do
  use GenServer

  def start_link(arg) do  

    GenServer.start_link(
      __MODULE__, 
      arg[:init_state_with], 
      name: arg[:name_to_register])                                       

  end

  ## Callbacks

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_call(:get_updates, _from, state) do
    {:reply, state, state}
  end

  @impl true
  def handle_cast({:push, item}, state) do
    {:noreply, [item | state]}
  end


  ##User interface:

  def get() do
    GenServer.call(__MODULE__, :get_updates)
  end

  def add(item) do
    GenServer.cast(__MODULE__, {:push, item})
  end

end

lib / servers / current_state.ex:

defmodule Servers.CurrentState do
  use GenServer

  def start_link(args) do  

    GenServer.start_link(
      __MODULE__, 
      args[:init_state_with], 
      name: args[:name_to_register])

  end

  ## Callbacks

  @impl true
  def init(state) do
    IO.inspect(state, label: "The CurrentState server is starting with state")
    {:ok, state}
  end

  @impl true
  def handle_call(:get_state, _from, state) do
    state_to_add = Servers.Updates.get()
    new_state = state_to_add ++ state

    {:reply, new_state, new_state}
  end


  ##User interface:

  def get() do
    GenServer.call(__MODULE__, :get_state)
  end

end

然后,您可以使用以下方法进行测试:

defmodule Servers.Go do
  def test() do
    IO.inspect("Updates has state: #{inspect Servers.Updates.get()}" )
    IO.inspect("CurrentState has state: #{inspect Servers.CurrentState.get()}" )
    :ok
  end
end

在iex中:

~/elixir_programs/servers$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Compiling 1 file (.ex)

The CurrentState server is starting with state: [:hello, 10]

Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> Servers.Go.test()
"Updates has state: [:goodbye]"
"CurrentState has state: [:goodbye, :hello, 10]"
:ok

iex(2)> 

(请注意,输出的第一行与服务器启动消息混合在一起。)

但是,您可以使用__MODULE__来简化操作:

application.ex

children = [
  # Starts a worker by calling: Servers.Worker.start_link(arg)
  # {Servers.Worker, arg},

  { Servers.CurrentState,  [:hello, 10] }
  { Servers.Updates, [:goodbye] }

]

lib / servers / updates.ex

defmodule Servers.Updates do
  use GenServer

  def start_link(arg) do  
                  #arg comes from child specification tuple
                  #inside the `children` list in application.ex

    #                        | module where the GenServer is defined
    #                        | 
    #                        |        | send arg to the GenServer's init() function       
    #                        V        V
    GenServer.start_link(__MODULE__, arg, name: __MODULE__)
    #                                      ^
    #                                      |
    #                     register the specified name for this GenServer

  end

  ## Callbacks

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_call(:get_updates, _from, state) do
    {:reply, state, state}
  end

  @impl true
  def handle_cast({:push, item}, state) do
    {:noreply, [item | state]}
  end

  ## User interface:

  def get() do
    GenServer.call(__MODULE__, :get_updates)
  end

  def add(item) do
    GenServer.cast(__MODULE__, {:push, item})
  end

end

lib / servers / current_state.ex:

defmodule Servers.CurrentState do
  use GenServer

  def start_link(arg) do  
                  #arg comes from child specification tuple
                  #inside the `children` list in application.ex

    #                        | module where the GenServer is defined
    #                        | 
    #                        |        | send arg to the GenServer's init() function       
    #                        V        V
    GenServer.start_link(__MODULE__, arg, name: __MODULE__)
    #                                       ^
    #                                       |
    #                     register the specified name for this GenServer 
  end

  ## Callbacks

  @impl true
  def init(state) do
    IO.inspect(state, label: "The CurrentState server is starting with state")
    {:ok, state}
  end

  @impl true
  def handle_call(:get_state, _from, state) do
    state_to_add = Servers.Updates.get()
    new_state = state_to_add ++ state

    {:reply, new_state, new_state}
  end

  ## User interface:

  def get() do
    GenServer.call(__MODULE__, :get_state)
  end

end