Подтвердить что ты не робот

Как периодически выполнять действия с Erlang gen_server?

Я хочу запустить gen_server, который, кроме того, будет выполнять одно действие каждую минуту.

Каков наилучший способ запланировать это?

4b9b3361

Ответ 1

У вас есть две простые альтернативы, используйте timer:send_interval/2 или erlang:send_after/3. send_interval проще настроить, а send_after (при использовании в модуле Erlang) более надежна, поскольку является встроенной функцией, см. Эффективность Руководство.

Использование send_after также гарантирует, что процесс gen_server не будет перегружен. Если вы использовали функцию send_interval, вы получили бы сообщение независимо от того, будет ли процесс поддерживать или нет. При вызове send_after непосредственно перед возвратом в handle_info вы планируете новое сообщение только после того, как вы обработали предыдущий. Если вы хотите более точное отслеживание времени, вы все равно можете запланировать send_after с динамическим временем, которое будет установлено ниже ?INTERVAL (или даже 0), чтобы догнать.

Я бы рекомендовал что-то в следующих строках в 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),
   ...

Вместо trigger вы можете отправить что-то с состоянием, если это необходимо, например {trigger, Count} или что-то.

Ответ 2

Чтобы точно управлять таймером, вы можете использовать erlang:start_timer и сохранить каждую созданную ссылку на таймер.

erlang:start_timer имеет небольшую разницу с erlang:send_after, см. http://www.erlang.org/doc/man/erlang.html#start_timer-3 и http://www.erlang.org/doc/man/erlang.html#send_after-3

Пример использования:

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

Ответ 3

В gen_server есть встроенный механизм для выполнения того же самого. Если третий элемент кортежа ответа методов init, handle_call, handle_cast или handle_info в gen_server является целым числом, сообщение timeout будет отправлено в процесс после этого периода времени в миллисекундах..., который должен быть обрабатывается с помощью handle_info. Например,

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