【问题标题】:What's the best way to do something periodically in Erlang?在 Erlang 中定期做某事的最佳方法是什么?
【发布时间】:2010-10-20 15:31:24
【问题描述】:

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

-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 消息,就不再生成超时,整个服务器就会停止运行。 (我可以通过在机器上获取外壳并手动发送“超时”消息来手动“除颤”它,但是...... eww。)

现在,我可以尝试记住始终在我的 Result 值中指定可选的 Timeout 参数。但这不成比例:总有一天我会忘记,并且会再次盯着这个错误。那么:有什么更好的方法?

我不认为我想编写一个永远运行并且大部分时间都在睡觉的实际循环;这似乎与 OTP 的精神背道而驰。

【问题讨论】:

    标签: erlang erlang-otp


    【解决方案1】:

    使用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}.
    

    【讨论】:

    • /me 拍了拍额头谢谢 :)
    • 除非您需要精确的亚毫秒级超时,然后您需要推出自己的解决方案
    • 我选择使用 erlang:send_after,而不是 timer:send_interval,我认为解释原因会很有启发性。这是因为我的 handle_info 可能需要很长时间才能完成,以至于在下一个间隔到来时它仍在运行,而且我不希望超时消息堆积在队列中。通过使用 erlang:send_after (一次在 init 函数中,再次在 handle_info(timeout, ...) 函数的末尾),我可以确保每次超时都在 至少 interval_milliseconds 之后前一个。这可能并不适合所有人,但似乎适合我。
    【解决方案2】:

    最好的办法是:

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

    【讨论】:

    • 我已经有几年没有接触 Erlang 了,但这看起来正是我最终要做的事情。谢谢。
    【解决方案3】:

    使用timer 模块:)

    【讨论】:

    猜你喜欢
    • 2010-11-04
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多