假设我有一个偶尔使用的流程。我应该顺其自然,还是将其状态保存到数据库并在一段时间后将其关闭?有 OTP 方法吗?
我想出了一个计时器进程的想法,它等待特定的时间,然后通知我的工作进程它必须关闭。这样,每个工作进程都会有一个计时器进程,每次工作进程执行某项操作时,它都会通知其计时器进程重置计时器。
感谢@Mike Quinlan,我找到了一种更好的方法来使用GenServer timeouts来做到这一点。我只是将超时值包含在handle_*回调的每个返回值中。例子:
defmodule Client do
use GenServer
@timeout 5000
def start_link() do
GenServer.start_link(__MODULE__, nil)
end
def hello(pid) do
GenServer.call(pid, :hello)
end
def init(state) do
{:ok, state}
end
def handle_call(:hello, _from, state) do
{:reply, :hello, state, @timeout}
end
def handle_info(:timeout, state) do
{:stop, :normal, state}
end
end
Run Code Online (Sandbox Code Playgroud)
每次我打电话时计时器都会重置hello/1。如果我在第一次调用后 5 秒内没有调用hello/1,则进程将关闭。
我正在开发一个跟踪用户的应用程序,我们完全按照您的指定进行操作。它将为用户聚合某些值,并在 60 秒后关闭。GenServers有一个回调handle_continue/2,您可以使用它来设置和重置进程的计时器。确保在DynamicSupervisor下启动进程并为它们提供重启策略:temporary
例子:
def MyProc do
use GenServer, restart: :temporary
@timeout :timer.seconds(60)
def start_link(opts) do
GenServer.start_link(__MODULE__, [], opts)
end
def init([]) do
# This will set the initial state and continue to set_timer
{:ok, %{timer: nil}, {:continue, :set_timer}}
end
def poke(pid) do
GenServer.call(pid, :poke)
end
def do_work(pid) do
GenServer.cast(pid, :do_work)
end
def handle_call(:poke, _from, :state) do
# We have received a call. Do the work and continue to set_timer
IO.puts("ouch")
{:reply, :ok, state, {:continue, :set_timer}}
end
def handle_cast(:do_work, state) do
IO.puts("Working...")
# You can continue from handle_cast/2 & handle_info/2 and even handle_continue/2
{:noreply, state, {:continue, :set_timer}}
end
def handle_info(:graceful_stop, state) do
IO.inspect("PID #{inspect(self()} received inactivity shutdown. Bye!")
{:shutdown, :normal, state}
end
def handle_continue(:set_timer, state) do
# Forget the old timer if it exists
case state do
%{timer: timer} when is_reference(timer) -> :timer.cancel(timer)
_ -> nil
end
# Send this process the stop message after the timeout expires
timer_ref = Process.send_after(self(), @timeout, :graceful_stop)
{:noreply, %{state | timer: timer_ref}}
end
end
Run Code Online (Sandbox Code Playgroud)
出于多种原因,handle_continue 回调很有用。首先,它不会阻塞回调的调用过程init/1。GenServer 的任何繁重初始化都应该使用handle_continue 完成。其次,它允许进程在回复调用者后继续工作。例如,我们不希望用户等待此进程的响应,因为我们正在重置计时器。第三,它允许轻松共享代码。您所要做的就是{:continue, :set_timer}在每个指示进程应保持活动状态的操作之后放置,并且您可以确信超时将会延长。