2017-06-27 2 views
1

Ich versuche einen einfachen Oszillator zu modellieren, der ständig im Hintergrund läuft (eine Sinusfunktion integriert). Irgendwann möchte ich jedoch seinen Wert (Spannung und Zeit) anfordern können, der in seinem internen Zustand gehalten wird. Das liegt daran, dass ich zu einem späteren Zeitpunkt einen Pool von überwachten Oszillatoren haben möchte, und ihr Supervisor wird die Spannung/Werte und andere Handvoll-Operationen mitteln.Elixir/OTP kontinuierlicher Hintergrundjob und Zustandssuche

Ich habe diesen Ansatz erreicht, mit dem ich nicht 100% zufrieden bin, da es ein wenig mühsam ist, run() auszuführen, bevor die Serverimplementierung get_state beendet wird, d. handle_call({:get_state, pid}.....).

Gibt es einen anderen Ansatz, den ich versuchen könnte?

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 

Update 1

Ansatz das "Ticken" zu einem darunter liegenden Process dank der reply ich bei ElixirForum empfangen zu delegieren.

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 

Antwort

1

Um die Dinge einfacher zu machen, ist es immer gut, so wenig Abstraktionsstufen wie möglich zu verwenden. Sie brauchen grundsätzlich zwei verschiedene Prozesse: einen zu kreuzen und einen zu konsumieren. Auf diese Weise wird der Verbraucher nur dann verantwortlich sein, einen Zustand zu handhaben, und die „Ticker“ wird es nur mit Abständen ping angegeben:

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) 

Der Ausgang wäre:

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>] 

Die Umsetzung mit GenServer s sollte jetzt ziemlich einfach sein.

+0

Danke dafür! Ich habe die Frage mit dem neuen Ansatz aktualisiert, der konzeptionell nahe bei Ihnen liegt. Ich möchte Sie bitten, folgendes zu tun: 1) Bei Ihrer Herangehensweise binden Sie die Logik des "tickenden" ('{: ping}') und des "state retrieval" ('{: state, caller}') in die gleiche 'loop/receive' Funktion. Wenn es zwei verschiedene Prozesse gibt, ist es nicht besser, die Logik zu trennen (wie im Update1 zu meiner Frage)? 2) In Anbetracht dessen wird ein Pool von Oszillatoren sein, die autonom laufen, idealerweise überwacht (daher neu gestartet, wenn das Ticken fehlschlägt, usw.). Ist es besser, mit "Process" oder mit "Task" zu gehen? – lllllll

+0

Es sollte im gleichen "erhalten" sein, weil die Aufgabe bereit sein soll, auf beide zu antworten. Behandle dies als zwei verschiedene 'handle_call' Implementierungen. "Task" oder "Process" oder "GenServer" oder was auch immer ist eine Frage der Gewohnheit und der persönlichen Wahl. Die Antwort wäre sehr voreingenommen und sehr meinungsorientiert. – mudasobwa

Verwandte Themen