从另一个 GenServer 调用 GenServer



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

每次调用"状态","状态">

都应调用"更新"并在返回实际状态之前更新自身。

两个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.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

State"以"无进程"错误终止。有什么建议吗?

根据主管文档,children列表:

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

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

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

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

:start- 一个元组,其中包含要调用的模块函数参数来启动 子进程。此密钥是必需的。

:restart- 定义终止的子进程何时应 重新启动(请参阅下面的"重新启动值"部分)。此键是 可选,默认为 :p。

:shutdown- 定义子进程应该如何的原子 已终止(请参阅下面的"关机值"部分)。此键是 可选,如果类型为 :worker 或 :无穷大,则默认为 5000,如果 类型为 :主管。

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

还有第六个键:modules,它很少改变。它被设定 自动基于 :start 中的值。

请注意,没有name:密钥,您在子规范中列出了该密钥。

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

start_link/3 和 start/3 都支持 GenServer 注册名称 通过 :name 选项启动。注册名称也将自动生效 终止时已清理。支持的值包括:

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

{:global, term}- GenServer 使用 :global 模块中的函数使用给定术语全局注册。

{:via, module, term}- GenServer 使用给定的机制和名称注册。:via 选项需要一个导出的模块 register_name/2、unregister_name/1、whereis_name/1 和发送/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
]
}
]

然后你可以像这样定义你的两个GenServers:

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__来简化操作:

应用程序.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

最新更新