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