从另一个 GenServer 调用一个 GenServer
Calling a GenServer from another GenServer
我有一个使用 2 个 GenServer 的项目
第一个名为 "State" 的 GenServer 维护状态,第二个名为 "Updates" 的 GenServer 维护状态可能更新的列表。我想要实现的是:
每次调用 "State" 时,"State" 应该调用 "Updates" 并在返回实际状态之前更新自身。
两个 GenServers 都是由主管启动的,我可以从外部按名称调用两个 GenServers,但是如果我尝试在 "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
在"State"我有回调
@impl true
def handle_call({:get}, _from, state) do
updates = Updates.get_updates(Updates)
{:reply, updates, state}
end
同样,如果我直接从 iex 调用 Updates.get_updates(Updates),一切都会按预期进行,所以我认为我的主管一切都很好。好像 "State" 不知道 "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" terminates with a "no process" error. Any suggestions?
根据Supervisor docs,children
列表:
children = [
{Updates, name: Updates},
{State, name: State}
]
应该是 child specification
元组的列表,其中子规范具有以下有效键:
The child specification contains 6 keys. The first two are required,
and the remaining ones are optional:
:id - any term used to identify the child specification internally by
the supervisor; defaults to the given module. In the case of
conflicting :id values, the supervisor will refuse to initialize and
require explicit IDs. This key is required.
:start - a tuple with the module-function-args to be invoked to start
the child process. This key is required.
:restart - an atom that defines when a terminated child process should
be restarted (see the “Restart values” section below). This key is
optional and defaults to :permanent.
:shutdown - an atom that defines how a child process should be
terminated (see the “Shutdown values” section below). This key is
optional and defaults to 5000 if the type is :worker or :infinity if
the type is :supervisor.
:type - specifies that the child process is a :worker or a
:supervisor. This key is optional and defaults to :worker.
There is a sixth key, :modules, that is rarely changed. It is set
automatically based on the value in :start.
请注意,您在子规范中列出的 name:
键没有。
但是,GenServer.start_link()
确实有一个 name:
选项:
Both start_link/3 and start/3 support the GenServer to register a name
on start via the :name option. Registered names are also automatically
cleaned up on termination. The supported values are:
an atom - the GenServer is registered locally with the given name using Process.register/2.
{:global, term} - the GenServer is registered globally with the given term using the functions in the :global module.
{:via, module, term} - the GenServer is registered with the given mechanism and name. The :via option expects a module that exports
register_name/2, unregister_name/1, whereis_name/1 and send/2. One
such example is the :global module which uses these functions for
keeping the list of names of processes and their associated PIDs that
are available globally for a network of Elixir nodes. Elixir also
ships with a local, decentralized and scalable registry called
Registry for locally storing names that are generated dynamically.
For example, we could start and register our Stack server locally as
follows:
# 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
我有一个使用 2 个 GenServer 的项目 第一个名为 "State" 的 GenServer 维护状态,第二个名为 "Updates" 的 GenServer 维护状态可能更新的列表。我想要实现的是:
每次调用 "State" 时,"State" 应该调用 "Updates" 并在返回实际状态之前更新自身。
两个 GenServers 都是由主管启动的,我可以从外部按名称调用两个 GenServers,但是如果我尝试在 "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
在"State"我有回调
@impl true
def handle_call({:get}, _from, state) do
updates = Updates.get_updates(Updates)
{:reply, updates, state}
end
同样,如果我直接从 iex 调用 Updates.get_updates(Updates),一切都会按预期进行,所以我认为我的主管一切都很好。好像 "State" 不知道 "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" terminates with a "no process" error. Any suggestions?
根据Supervisor docs,children
列表:
children = [
{Updates, name: Updates},
{State, name: State}
]
应该是 child specification
元组的列表,其中子规范具有以下有效键:
The child specification contains 6 keys. The first two are required, and the remaining ones are optional:
:id - any term used to identify the child specification internally by the supervisor; defaults to the given module. In the case of conflicting :id values, the supervisor will refuse to initialize and require explicit IDs. This key is required.
:start - a tuple with the module-function-args to be invoked to start the child process. This key is required.
:restart - an atom that defines when a terminated child process should be restarted (see the “Restart values” section below). This key is optional and defaults to :permanent.
:shutdown - an atom that defines how a child process should be terminated (see the “Shutdown values” section below). This key is optional and defaults to 5000 if the type is :worker or :infinity if the type is :supervisor.
:type - specifies that the child process is a :worker or a :supervisor. This key is optional and defaults to :worker.
There is a sixth key, :modules, that is rarely changed. It is set automatically based on the value in :start.
请注意,您在子规范中列出的 name:
键没有。
但是,GenServer.start_link()
确实有一个 name:
选项:
Both start_link/3 and start/3 support the GenServer to register a name on start via the :name option. Registered names are also automatically cleaned up on termination. The supported values are:
an atom - the GenServer is registered locally with the given name using Process.register/2.
{:global, term} - the GenServer is registered globally with the given term using the functions in the :global module.
{:via, module, term} - the GenServer is registered with the given mechanism and name. The :via option expects a module that exports register_name/2, unregister_name/1, whereis_name/1 and send/2. One such example is the :global module which uses these functions for keeping the list of names of processes and their associated PIDs that are available globally for a network of Elixir nodes. Elixir also ships with a local, decentralized and scalable registry called Registry for locally storing names that are generated dynamically.
For example, we could start and register our Stack server locally as follows:
# 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