在Erlang中,定期执行某项任务的最佳方法是什么?

33

我有一个进程需要每十五秒做一些工作。我目前是这样做的:

-behavior(gen_server).

interval_milliseconds ()-> 15000.
init()->
    {ok, 
     _State = FascinatingStateData,
     _TimeoutInterval = interval_milliseconds ()
    }.

%% This gets called automatically as a result of our handlers
%% including the optional _TimeoutInterval value in the returned
%% Result
handle_info(timeout, StateData)->
    {noreply, 
     _State = do_some_work(StateData),
      _TimeoutInterval = interval_milliseconds ()
    }.

这个方法可以运行,但是非常脆弱:如果我想让我的服务器学习一条新的消息,当我编写任何新的处理函数时,就必须记得在其返回值中包含可选的超时时间间隔。也就是说,如果我正在处理同步调用,我需要这样做:

%% Someone wants to know our state; tell them
handle_call(query_state_data, _From, StateData)->
    {reply, StateData, _NewStateData = whatever (), interval_milliseconds ()};

而不是

%% Someone wants to know our state; tell them
handle_call(query_state_data, _From, StateData)->
    {reply, StateData, _NewStateData = whatever ()};

可能你已经猜到了,我犯过这样的错误很多次。这种错误很讨厌,因为一旦代码处理了query_state_data消息,超时就不会再生成,整个服务器就会停止运行。(我可以通过在机器上获取shell并手动发送“timeout”消息来进行手动“除颤”,但是…呃。)

现在,我可以尝试记住始终在我的Result值中指定可选的Timeout参数。 但是这样做无法扩展:总有一天我会忘记,并再次面对这个错误。那么:有更好的方法吗?

我认为我不想编写实际上永远运行并花费大部分时间睡眠的循环;那似乎与OTP的精神相悖。

3个回答

42

使用timer:send_interval/2。例如:

-behavior(gen_server).

interval_milliseconds()-> 15000.
init()->
    timer:send_interval(interval_milliseconds(), interval),
    {ok, FascinatingStateData}.

%% this clause will be called every 15 seconds
handle_info(interval, StateData)->
    State2 = do_some_work(StateData)
    {noreply, State2}.

1
/me 拍了拍额头 - offby1
2
除非你需要精确的亚毫秒超时,否则你需要自己编写解决方案。 - Rob Elsner
22
我选择使用erlang:send_after而不是timer:send_interval,并且想解释一下其中的原因。这是因为我的handle_info可能需要很长时间才能完成,以至于当下一个interval到来时,它还在运行,我不希望超时消息在队列中堆积。通过在init函数中使用erlang:send_after(仅一次),并在handle_info(timeout,...)函数的末尾再次使用,我可以确保每个超时时间距离上一个超时时间_至少_间隔毫秒。这种做法可能不适用于所有人,但对我来说似乎是正确的。 - offby1

24

最好的方法是:

init([]) ->
  Timer = erlang:send_after(1, self(), check),
  {ok, Timer}.

handle_info(check, OldTimer) ->
  erlang:cancel_timer(OldTimer),
  do_task(),
  Timer = erlang:send_after(1000, self(), check),
  {noreply, Timer}.

2
我已经有几年没有接触 Erlang 了,但这看起来就是我最终要做的事情。谢谢。 - offby1
为了完整起见,在handle_info/2回调中不需要使用erlang:cancel_timer/1。通常,返回的计时器引用用于在消息实际发送之前取消计时器。一旦在handle_info中收到消息,计时器已经过期,因此erlang:cancel_timer/1调用是无操作的。 - goncalotomas
为了完整起见,在handle_info/2回调中不需要使用erlang:cancel_timer/1。通常,返回的计时器引用用于在消息实际发送之前取消计时器。一旦在handle_info中收到消息,计时器已经过期,因此erlang:cancel_timer/1调用是无操作的。 - undefined

7

1
这不是最佳解决方案:http://erlang.org/doc/efficiency_guide/commoncaveats.html#id60206 - mspanc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接