2017-06-27 10 views
1

私はバックグラウンドで連続して実行されている単純なオシレーターをモデル化しようとしています(サイン関数を統合しています)。しかし、ある時点では、内部の状態に保たれたその値(電圧と時間)を要求できるようにしたいと考えています。後者の点では、オシレータのプールを監視し、スーパーバイザが電圧/値、および他の一握りの操作を平均するためです。エリクサー/ OTP連続バックグラウンドジョブと状態の参照

私はこのアプローチに達しました。これはget_stateサーバの実装を終了する前にrun()を実行する必要があるため、私は100%満足していません。 handle_call({:get_state, pid}.....)

私は試してみることができる他のアプローチはありますか?基礎となるProcessに "カチカチ" を委任

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のおかげ。

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 

答えて

1

可能な限り抽象度を低くすると、操作が簡単になります。基本的に2つの異なるプロセスが必要です.1つはティック、もう1つは消費します。こうすることで、消費者は「ティッカー」は単に指定された間隔でそれを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 sの実装今はかなり簡単です。

+0

ありがとうございます!私は新しいアプローチで質問を更新しました。これは概念的にあなたのものに近いものです。私はあなたに質問したいと思います:1)あなたのアプローチでは、 "ticking"( '{:ping}')と "state retrieval"( '{state、caller}')の両方のロジックを同じ「ループ/受信」機能。 2つの異なるプロセスがあるとすれば、ロジックを(Update1のように)私の質問と区別する方が良いのではないでしょうか? 2)これは自律的に動作するオシレータのプールであり、理想的には監督されていることを考慮してください(したがって、ティッキングが失敗した場合などに再起動します)。 'Process'または' Task'を使う方が良いでしょうか? – lllllll

+0

タスクは両方に応答する準備ができているので、同じ 'receive 'になければなりません。これを2つの異なる 'handle_call'実装として扱います。 'タスク'、 'プロセス'、 'ジェンサーバー'など、習慣や個人的な選択の問題があります。その答えは非常に偏っており、非常に意見に基づいています。 – mudasobwa

関連する問題