我们如何使用GenServer实现可重置的倒数计时器?
1)在固定的时间后执行任务,比如说每60秒
2)有一种方法可以在计时器过去之前将倒计时重置为60秒
我看过How to perform actions periodically with Erlang's gen_server?,但它并没有完全涵盖在倒计时结束前休息计时器的方面。
感谢。
答案 0 :(得分:11)
How to run some code every few hours in Phoenix framework?定义了如何定期工作。
要使用该作为基础实现取消,您可以执行以下操作:
defmodule MyApp.Periodically do
use GenServer
def start_link() do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
def reset_timer() do
GenServer.call(__MODULE__, :reset_timer)
end
def init(state) do
timer = Process.send_after(self(), :work, 60_000)
{:ok, %{timer: timer}}
end
def handle_call(:reset_timer, _from, %{timer: timer}) do
:timer.cancel(timer)
timer = Process.send_after(self(), :work, 60_000)
{:reply, :ok, %{timer: timer})
end
def handle_info(:work, state) do
# Do the work you desire here
# Start the timer again
timer = Process.send_after(self(), :work,60_000)
{:noreply, %{timer: timer}}
end
# So that unhanded messages don't error
def handle_info(_, state) do
{:ok, state)
end
end
这保持对计时器的引用,允许它被取消。每次收到:work
消息时,都会创建一个新计时器并将其存储在GenServer的状态中。
答案 1 :(得分:4)
如果你在Erlang中这样做,根据你提到的另一个问题......
您保存计时器参考并调用erlang:cancel_timer / 1以阻止它被触发(如果它还没有)。你必须注意这个'已经被解雇'的竞争条件,当取消定时器时,触发消息已经在你的消息队列中了。您可能会或可能不会关心这一点,但是如果在取消触发器之后永远不执行操作很重要,那么您需要在设置定时消息时创建引用(或者您可以使用计数器),以及何时你得到一个触发器,你必须检查它是否与最新的相关。
其他问题的代码变为:
-define(INTERVAL, 60000). % One minute
init(Args) ->
...
% Start first timer
MyRef = erlang:make_ref(),
{ok, TRef} = erlang:send_after(?INTERVAL, self(), {trigger, MyRef}),
...
{ok, State#your_record{
timer = TRef,
latest = MyRef
}}.
% Trigger only when the reference in the trigger message is the same as in State
handle_info({trigger, MyRef}, State = #your_record{latest=MyRef}) ->
% Do the action
...
% Start next timer
MyRef = erlang:make_ref(),
{ok, TRef} = erlang:send_after(?INTERVAL, self(), trigger),
...
{ok, State#your_record{
timer = TRef,
latest = MyRef
}}.
% Ignore this trigger, it has been superceeded!
handle_info({trigger, _OldRef}, State) ->
{ok, State}.
这样的事情可以重置计时器:
handle_info(reset, State = #your_record{timer=TRef}) ->
% Cancel old timer
erlang:cancel_timer(TRef),
% Start next timer
MyNewRef = erlang:make_ref(),
{ok, NewTRef} = erlang:send_after(?INTERVAL, self(), trigger),
{ok, State#your_record{
timer = NewTRef,
latest = MyNewRef
}}.
拨打电话可能更适合取消功能,但这取决于您的应用,因此取决于您。
从技术上讲,没有必要取消定时器,因为一旦你用你的新引用创建了一个新的状态,即使旧的定时器继续运行,它会在它触发时被忽略,但我认为最好是真的整理一下。