Erlang Elixir/OTP连续后台作业和状态查找

Erlang Elixir/OTP连续后台作业和状态查找,erlang,elixir,otp,gen-server,Erlang,Elixir,Otp,Gen Server,我试图模拟一个在后台连续运行的简单振荡器(集成正弦函数)。然而,在某个时刻,我希望能够请求它的值(电压和时间),它保持在它的内部状态。这是因为在后一点上,我希望有一个振荡器池受到监督,他们的监督将平均电压/值和其他操作 我采用了这种方法,但我不是100%满意,因为在退出get_state服务器实现之前必须运行run()有点痛苦,即handle_调用({:get_state,pid}…) 有没有其他方法我可以试试 defmodule World.Cell do use GenServer

我试图模拟一个在后台连续运行的简单振荡器(集成正弦函数)。然而,在某个时刻,我希望能够请求它的值(电压和时间),它保持在它的内部状态。这是因为在后一点上,我希望有一个振荡器池受到监督,他们的监督将平均电压/值和其他操作

我采用了这种方法,但我不是100%满意,因为在退出
get_state
服务器实现之前必须运行
run()
有点痛苦,即
handle_调用({: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

为了使事情变得更简单,最好使用尽可能少的抽象级别。你基本上需要两个不同的过程:一个打勾,一个消费。这样,消费者将只负责处理一个状态,“股票行情器”只需按指定的时间间隔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>]
电压:42
勾选:[间隔:500,pid:#pid]
勾选:[间隔:500,pid:#pid]
电压:44
勾选:[间隔:500,pid:#pid]
勾选:[间隔:500,pid:#pid]
电压:46
勾选:[间隔:500,pid:\35; pid]
勾选:[间隔:500,pid:#pid]

使用
GenServer
s的实现现在应该非常简单。

谢谢!我用新方法更新了这个问题,这在概念上与你的方法相近。我想问您:1)在您的方法中,您将“滴答声”(
{:ping}
)和“状态检索”(
{:state,caller}
)的逻辑嵌入到同一个
循环/接收
函数中。考虑到这是两个不同的过程,分离逻辑(如我的问题的更新1)不是更好吗?2) 考虑到这将是一个独立运行的振荡器池,理想情况下受监督(因此,如果滴答失败等,将重新启动)。使用
过程
还是使用
任务
更好?它应该在同一个
接收
中,因为任务要准备好响应这两个任务。将其视为两种不同的
handle\u调用
实现<代码>任务或
过程
GenServer
或任何习惯和个人选择的问题。答案将是非常有偏见和非常基于意见的。
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>]