代码之家  ›  专栏  ›  技术社区  ›  Sorebrez

从另一个genserver调用genserver

  •  1
  • Sorebrez  · 技术社区  · 7 年前

    我有一个项目,我用两台发电机 名为“state”的第一个genserver维护状态,名为“updates”的第二个genserver维护状态的可能更新列表。我想要实现的是:

    每次调用“state”时,“state”都应该调用“updates”并在返回实际状态之前自我更新。

    两个genserver都是由一个主管启动的,我可以从外部通过名称调用这两个genserver,但如果我尝试在“state”内部调用“updates”的API,则“state”将以“no process”错误终止。有什么建议吗?

      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
    

    同样,如果我调用updates.get_updates(更新),例如,从iex直接获得所有工作正常,所以我认为我的主管一切正常。似乎“状态”不知道“更新”的名称。

    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 回复  |  直到 7 年前
        1
  •  1
  •   7stud    7 年前

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

    根据 Supervisor docs , the children 名单:

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

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

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

    :ID -用于在内部标识子规范的任何术语 管理器;默认为给定的模块。在情况下 冲突:ID值,主管将拒绝初始化和 需要显式ID。此密钥是必需的。

    开始 -要调用模块函数参数以启动的元组 子进程。此密钥是必需的。

    重新启动 -定义终止的子进程何时应 重新启动(参见下面的__restart values_部分)。这把钥匙是 可选,默认为:永久。

    关机 -定义子进程应如何 终止(见下文__shutdown values_一节)。这把钥匙是 可选,如果类型为:worker或:infinity if,则默认为5000。 类型为:主管。

    类型 -指定子进程是:worker或 主管。此键是可选的,默认为:worker。

    有第六把钥匙, 模块 这是很少改变的。它被设定 根据:Start中的值自动创建。

    注意没有 name: 您在子规范中列出的密钥。

    然而, GenServer.start_link() 确实有 姓名: 选项:

    start-link/3和start/3都支持genserver注册名称 通过:name选项启动时。注册的名称也会自动 终止时清理。支持的值包括:

    原子 -genserver使用process.register/2以给定的名称在本地注册。

    {:Global,Type } -genserver使用:global模块中的函数以给定的术语全局注册。

    :通过,模块,术语 -genserver以给定的机制和名称注册。:via选项需要一个导出的模块 注册_name/2,注销_name/1,其中是_name/1和send/2。一 例如:全局模块,它使用这些函数 保留进程及其相关PID的名称列表 在全球范围内可用于elixir节点网络。灵丹妙药 提供本地、分散和可扩展的注册中心 用于本地存储动态生成的名称的注册表。

    例如,我们可以在本地启动和注册堆栈服务器 跟随:

    # 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中启动_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__ 简化工作:

    应用程序 :

    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