How to perform actions periodically with Erlang's gen_server?

前端 未结 4 778
伪装坚强ぢ
伪装坚强ぢ 2020-12-23 20:32

I want to start a gen_server that additionally, will perform one action every minute.

What is the best way to schedule that?

相关标签:
4条回答
  • 2020-12-23 20:52

    There is actually a built-in mechanism within gen_server to accomplish the same thing. If the third element of response tuple of the init, handle_call, handle_cast or handle_info methods in the gen_server is an integer, a timeout message wil be sent to the process after that period of time in millisecs... which should be handled using handle_info. For eg :

    init(Args) ->
       ... % Start first timer
       {ok, SomeState, 20000}. %% 20000 is the timeout interval
    
    handle_call(Input, From, State) ->
       ... % Do something
       ... % Do something else
       {reply, SomeState, 20000}. %% 20000 is the timeout interval
    
    handle_cast(Input, State) ->
       ... % Do something
       ... % Do something else
       {noreply, SomeState, 20000}. %% 20000 is the timeout interval
    
    
    %% A timeout message is sent to the gen_server to be handled in handle_info %%
    handle_info(timeout, State) ->
       ... % Do the action
       ... % Start new timer
       {noreply, SomeState, 20000}. %% "timeout" can be sent again after 20000 ms
    
    
    0 讨论(0)
  • 2020-12-23 20:58

    There is also the timer module, which could be used.

    http://erldocs.com/R14B02/stdlib/timer.html?i=8&search=timer#cancel_timer/1

    0 讨论(0)
  • 2020-12-23 21:01

    To precisely control the timer, you may want to use erlang:start_timer, and save each timer reference you have created.

    erlang:start_timer has a tiny difference with erlang:send_after, see http://www.erlang.org/doc/man/erlang.html#start_timer-3 and http://www.erlang.org/doc/man/erlang.html#send_after-3

    Example use case:

    init(Args) ->
        ...
        TRef = erlang:start_timer(?INTERVAL, self(), trigger),
        State = #state{tref = TRef},
        ...
    
    handle_info({timeout, _Ref, trigger}, State) ->
        %% With this cancel call we are able to manually send the 'trigger' message 
        %% to re-align the timer, and prevent accidentally setting duplicate timers
        erlang:cancel(State#state.tref),
        ...
        TRef = erlang:start_timer(?INTERVAL, self(), trigger),
        NewState = State#state{tref = TRef},
        ...
    
    handle_cast(stop_timer, State) ->
        TRef = State#state.tref,
        erlang:cancel(TRef),
    
        %% Remove the timeout message that may have been put in our queue just before 
        %% the call to erlang:cancel, so that no timeout message would ever get 
        %% handled after the 'stop_timer' message
        receive
            {timeout, TRef, _} -> void
            after 0 -> void
        end,
        ...
    
    0 讨论(0)
  • 2020-12-23 21:04

    You have two easy alternatives, use timer:send_interval/2 or erlang:send_after/3. send_interval is easier to setup, while send_after (when used in the Erlang module) is more reliable since it is a built-in function, see the Efficiency Guide.

    Using send_after also ensures that the gen_server process is not overloaded. If you were using the send_interval function you would get a message regardless if the process can keep up or not. With send_after being called just before the return in handle_info you only schedule a new message once you handled the previous one. If you want more accurate time tracking you can still schedule a send_after with the time set dynamically to something lower than ?INTERVAL (or even 0) to catch up.

    I would recommend something along the following lines in your gen_server:

    -define(INTERVAL, 60000). % One minute
    
    init(Args) ->
       ... % Start first timer
       erlang:send_after(?INTERVAL, self(), trigger),
       ...
    
    handle_info(trigger, State) ->
       ... % Do the action
       ... % Start new timer
       erlang:send_after(?INTERVAL, self(), trigger),
       ...
    

    Instead of trigger you could send something with a state if it is needed, like {trigger, Count} or something.

    0 讨论(0)
提交回复
热议问题