Как периодически выполнять действия с помощью gen_server Эрланга?
Я хочу запустить gen_server, который дополнительно будет выполнять одно действие каждую минуту.
Каков наилучший способ запланировать это?
4 ответа
У вас есть две простые альтернативы, используйте 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}
или что-то.
Чтобы точно контролировать таймер, вы можете использовать erlang:start_timer
и сохраните каждую ссылку на таймер, которую вы создали.
erlang:start_timer
имеет небольшую разницу с erlang:send_after
см. http://www.erlang.org/doc/man/erlang.html и http://www.erlang.org/doc/man/erlang.html
Пример использования:
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,
...
На самом деле в gen_server есть встроенный механизм для выполнения той же задачи. Если третий элемент ответного кортежа из методов init, handle_call, handle_cast или handle_info в gen_server
является целым числом, timeout
сообщение будет отправлено процессу через этот промежуток времени в миллисекундах... которое должно быть обработано с помощью handle_info. Например:
init (Args) -> ... % Запустить первый таймер {ОК, SomeState, 20000}. %% 20000 - интервал ожидания handle_call(Вход, От, Состояние) -> ... % Сделай что-нибудь...% Сделай что-нибудь еще {ответить, SomeState, 20000}. %% 20000 - интервал ожидания handle_cast(вход, состояние) -> ... % Сделай что-нибудь...% Сделай что-нибудь еще {Noreply, SomeState, 20000}. %% 20000 - интервал ожидания %% Сообщение о тайм-ауте отправляется на сервер gen_server для обработки в handle_info %% handle_info(время ожидания, штат) -> ... % Выполните действие...% Запустить новый таймер {Noreply, SomeState, 20000}. %% "timeout" может быть отправлен снова через 20000 мс
Также есть timer
модуль, который можно было бы использовать.
http://erldocs.com/R14B02/stdlib/timer.html?i=8&search=timer