Elixir/OTP 连续后台作业和状态查找
Elixir/OTP continuous background job and state lookup
我正在尝试模拟一个在后台连续 运行ning 的简单振荡器(集成正弦函数)。但是在某些时候,我希望能够请求它的值(电压和时间),该值保持在其内部状态。那是因为在后面的一点上,我需要一个受监督的振荡器池,他们的主管将对 voltage/values 和其他一些操作进行平均。
我采用了这种方法,对此我不是 100% 满意,因为在退出 get_state
服务器实施之前必须 运行 run()
有点痛苦, IE。 handle_call({:get_state, pid}.....)
.
还有其他方法可以尝试吗?
defmodule World.Cell do
use GenServer
@timedelay 2000
# API #
#######
def start_link do
GenServer.start_link(__MODULE__, [], [name: {:global, __MODULE__}])
end
def run do
GenServer.cast({:global, __MODULE__}, :run)
end
def get_state(pid) do
GenServer.call(pid, {:get_state, pid})
end
# Callbacks #
#############
def init([]) do
:random.seed(:os.timestamp)
time = :random.uniform
voltage = :math.sin(2 * :math.pi + time)
state = %{time: time, voltage: voltage }
{:ok, state, @timedelay}
end
def handle_cast(:run, state) do
new_time = state.time + :random.uniform/12
new_voltage = :math.sin(2 * :math.pi + new_time)
new_state = %{time: new_time, voltage: new_voltage }
IO.puts "VALUES #{inspect self()} t/v #{new_time}/#{new_voltage}"
{:noreply, new_state, @timedelay}
end
def handle_info(:timeout, state) do
run() # <--------------------- ALWAYS HAVING TO RUN IT
{:noreply, state, @timedelay}
end
def handle_call({:get_state, pid}, _from, state) do
IO.puts "getting state"
run() # <--------------------- RUN UNLESS IT STOPS after response
{:reply, state, state}
end
end
更新 1
将 "ticking" 委托给基础 Process
的方法,感谢我在 ElixirForum 收到的 reply。
defmodule World.Cell do
use GenServer
@timedelay 2000
def start_link do
GenServer.start_link(__MODULE__, [], [name: {:global, __MODULE__}])
end
def get_state(pid) do
GenServer.call(pid, {:get_state, pid})
end
def init([]) do
:random.seed(:os.timestamp)
time = :random.uniform
voltage = :math.sin(2 * :math.pi + time)
timer_ref = Process.send_after(self(), :tick, @timedelay)
state = %{time: time, voltage: voltage, timer: timer_ref}
{:ok, state}
end
def handle_info(:tick, state) do
new_state = run(state)
timer_ref = Process.send_after(self(), :tick, @timedelay)
{:noreply, %{new_state | timer: timer_ref}}
end
def handle_call({:get_state, pid}, _from, state) do
IO.puts "getting state"
return = Map.take(state, [:time, :voltage])
{:reply, return, state}
end
defp run(state) do
new_time = state.time + :random.uniform/12
new_voltage = :math.sin(2 * :math.pi + new_time)
new_state = %{state | time: new_time, voltage: new_voltage}
IO.puts "VALUES #{inspect self()} t/v #{new_time}/#{new_voltage}"
new_state
end
end
为了让事情变得更简单,使用尽可能少的抽象级别总是好的。您基本上需要两个不同的进程:一个用于计费,一个用于消费。这样消费者将只负责处理一个状态,而“自动收报机”只会以指定的时间间隔对其进行 ping:
defmodule World.Cell do
@interval 500
def start_link do
{:ok, pid} = Task.start_link(fn ->
loop(%{time: :random.uniform, voltage: 42})
end)
Task.start_link(fn -> tick([interval: @interval, pid: pid]) end)
{:ok, pid}
end
# consumer’s loop
defp loop(map) do
receive do
{:state, caller} -> # state requested
send caller, {:voltage, Map.get(map, :voltage)}
loop(map)
{:ping} -> # tick
loop(map
|> Map.put(:voltage, map.voltage + 1)
|> Map.put(:time, map.time + :random.uniform/12))
end
end
# ticker loop
defp tick(init) do
IO.inspect init, label: "Tick"
send init[:pid], {:ping}
Process.sleep(init[:interval])
tick(init)
end
end
{:ok, pid} = World.Cell.start_link
(1..3) |> Enum.each(fn _ ->
{:state, _result} = send pid, {:state, self()}
receive do
{:voltage, value} -> IO.inspect value, label: "Voltage"
end
Process.sleep 1000
end)
输出将是:
Voltage: 42
Tick: [interval: 500, pid: #PID<0.80.0>]
Tick: [interval: 500, pid: #PID<0.80.0>]
Voltage: 44
Tick: [interval: 500, pid: #PID<0.80.0>]
Tick: [interval: 500, pid: #PID<0.80.0>]
Voltage: 46
Tick: [interval: 500, pid: #PID<0.80.0>]
Tick: [interval: 500, pid: #PID<0.80.0>]
使用 GenServer
的实现现在应该非常简单了。
我正在尝试模拟一个在后台连续 运行ning 的简单振荡器(集成正弦函数)。但是在某些时候,我希望能够请求它的值(电压和时间),该值保持在其内部状态。那是因为在后面的一点上,我需要一个受监督的振荡器池,他们的主管将对 voltage/values 和其他一些操作进行平均。
我采用了这种方法,对此我不是 100% 满意,因为在退出 get_state
服务器实施之前必须 运行 run()
有点痛苦, IE。 handle_call({:get_state, pid}.....)
.
还有其他方法可以尝试吗?
defmodule World.Cell do
use GenServer
@timedelay 2000
# API #
#######
def start_link do
GenServer.start_link(__MODULE__, [], [name: {:global, __MODULE__}])
end
def run do
GenServer.cast({:global, __MODULE__}, :run)
end
def get_state(pid) do
GenServer.call(pid, {:get_state, pid})
end
# Callbacks #
#############
def init([]) do
:random.seed(:os.timestamp)
time = :random.uniform
voltage = :math.sin(2 * :math.pi + time)
state = %{time: time, voltage: voltage }
{:ok, state, @timedelay}
end
def handle_cast(:run, state) do
new_time = state.time + :random.uniform/12
new_voltage = :math.sin(2 * :math.pi + new_time)
new_state = %{time: new_time, voltage: new_voltage }
IO.puts "VALUES #{inspect self()} t/v #{new_time}/#{new_voltage}"
{:noreply, new_state, @timedelay}
end
def handle_info(:timeout, state) do
run() # <--------------------- ALWAYS HAVING TO RUN IT
{:noreply, state, @timedelay}
end
def handle_call({:get_state, pid}, _from, state) do
IO.puts "getting state"
run() # <--------------------- RUN UNLESS IT STOPS after response
{:reply, state, state}
end
end
更新 1
将 "ticking" 委托给基础 Process
的方法,感谢我在 ElixirForum 收到的 reply。
defmodule World.Cell do
use GenServer
@timedelay 2000
def start_link do
GenServer.start_link(__MODULE__, [], [name: {:global, __MODULE__}])
end
def get_state(pid) do
GenServer.call(pid, {:get_state, pid})
end
def init([]) do
:random.seed(:os.timestamp)
time = :random.uniform
voltage = :math.sin(2 * :math.pi + time)
timer_ref = Process.send_after(self(), :tick, @timedelay)
state = %{time: time, voltage: voltage, timer: timer_ref}
{:ok, state}
end
def handle_info(:tick, state) do
new_state = run(state)
timer_ref = Process.send_after(self(), :tick, @timedelay)
{:noreply, %{new_state | timer: timer_ref}}
end
def handle_call({:get_state, pid}, _from, state) do
IO.puts "getting state"
return = Map.take(state, [:time, :voltage])
{:reply, return, state}
end
defp run(state) do
new_time = state.time + :random.uniform/12
new_voltage = :math.sin(2 * :math.pi + new_time)
new_state = %{state | time: new_time, voltage: new_voltage}
IO.puts "VALUES #{inspect self()} t/v #{new_time}/#{new_voltage}"
new_state
end
end
为了让事情变得更简单,使用尽可能少的抽象级别总是好的。您基本上需要两个不同的进程:一个用于计费,一个用于消费。这样消费者将只负责处理一个状态,而“自动收报机”只会以指定的时间间隔对其进行 ping:
defmodule World.Cell do
@interval 500
def start_link do
{:ok, pid} = Task.start_link(fn ->
loop(%{time: :random.uniform, voltage: 42})
end)
Task.start_link(fn -> tick([interval: @interval, pid: pid]) end)
{:ok, pid}
end
# consumer’s loop
defp loop(map) do
receive do
{:state, caller} -> # state requested
send caller, {:voltage, Map.get(map, :voltage)}
loop(map)
{:ping} -> # tick
loop(map
|> Map.put(:voltage, map.voltage + 1)
|> Map.put(:time, map.time + :random.uniform/12))
end
end
# ticker loop
defp tick(init) do
IO.inspect init, label: "Tick"
send init[:pid], {:ping}
Process.sleep(init[:interval])
tick(init)
end
end
{:ok, pid} = World.Cell.start_link
(1..3) |> Enum.each(fn _ ->
{:state, _result} = send pid, {:state, self()}
receive do
{:voltage, value} -> IO.inspect value, label: "Voltage"
end
Process.sleep 1000
end)
输出将是:
Voltage: 42
Tick: [interval: 500, pid: #PID<0.80.0>]
Tick: [interval: 500, pid: #PID<0.80.0>]
Voltage: 44
Tick: [interval: 500, pid: #PID<0.80.0>]
Tick: [interval: 500, pid: #PID<0.80.0>]
Voltage: 46
Tick: [interval: 500, pid: #PID<0.80.0>]
Tick: [interval: 500, pid: #PID<0.80.0>]
使用 GenServer
的实现现在应该非常简单了。