【问题标题】:Elixir/OTP continuous background job and state lookupElixir/OTP 连续后台作业和状态查找
【发布时间】:2017-11-30 21:56:30
【问题描述】:

我正在尝试模拟一个在后台连续运行的简单振荡器(集成正弦函数)。但是在某些时候,我希望能够请求它的值(电压和时间),它保持在其内部状态。那是因为在后面一点,我需要一个受监督的振荡器池,他们的监督员将平均电压/值和其他少数操作。

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

感谢我在 ElixirForum 收到的reply,将“滴答”委托给底层Process 的方法。

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

【问题讨论】:

    标签: erlang elixir erlang-otp gen-server


    【解决方案1】:

    为了让事情变得更简单,使用尽可能少的抽象级别总是好的。你基本上需要两个不同的过程:一个是打勾,一个是消费。这样,消费者将只负责处理一个状态,而“ticker”只会以指定的时间间隔对其进行 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>]
    

    GenServers 的实现现在应该非常简单。

    【讨论】:

    • 谢谢!我用新方法更新了这个问题,这在概念上接近你的。我想问你:1)在你的方法中,你在同一个loop/receive函数中嵌入了“滴答”({:ping})和“状态检索”({:state, caller})的逻辑。鉴于这是两个不同的过程,将逻辑分开不是更好吗(如我的问题的 Update1 中)? 2)考虑到这将是一个自主运行的振荡器池,理想情况下是受监督的(因此如果滴答失败等重新启动)。是Process 还是Task 更好?
    • 它应该在同一个receive 中,因为该任务准备好响应两者。将此视为两个不同的handle_call 实现。 TaskProcessGenServer 或任何习惯和个人选择的问题。答案将是高度偏见和非常基于意见的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-24
    • 2013-10-21
    • 1970-01-01
    • 1970-01-01
    • 2013-07-22
    • 2020-11-24
    • 1970-01-01
    相关资源
    最近更新 更多