Quiero iniciar gen_server que, además, realizará una acción cada minuto.¿Cómo realizar acciones periódicamente con gen_server de Erlang?
¿Cuál es la mejor manera de programar eso?
Quiero iniciar gen_server que, además, realizará una acción cada minuto.¿Cómo realizar acciones periódicamente con gen_server de Erlang?
¿Cuál es la mejor manera de programar eso?
Tienes dos alternativas fáciles, utiliza timer:send_interval/2
o erlang:send_after/3
. send_interval
es más fácil de instalar, mientras que send_after
(cuando se utiliza en el módulo de Erlang) es más fiable ya que es una función incorporada, ver el Efficiency Guide.
El uso de send_after
también asegura que el proceso gen_server
no esté sobrecargado. Si estuviera utilizando la función send_interval
, recibiría un mensaje independientemente de si el proceso puede mantenerse o no. Con send_after
ser llamado justo antes del regreso de handle_info
sólo se programe un nuevo mensaje una vez que manejaste la anterior. Si quieres que el tiempo de seguimiento más preciso todavía se puede programar una send_after
con el tiempo ajustado dinámicamente a algo más bajo que ?INTERVAL
(o incluso 0) para ponerse al día.
recomendaría algo a lo largo de las siguientes líneas en su 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),
...
En lugar de trigger
usted podría enviar algo con un estado si es necesario, al igual que {trigger, Count}
o algo así.
No es en realidad un mecanismo incorporado dentro gen_server a lograr lo mismo. Si el tercer elemento de la tupla de respuesta de los métodos init, handle_call, handle_cast o handle_info en el gen_server
es un número entero, se enviará un mensaje timeout
al proceso después de ese período de tiempo en milisegundos ... que debería manejarse usando handle_info . Para, por ejemplo:
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
¿por qué me dieron un voto de -1? –
Eso es verdad. Aunque significa que debe manipular el tiempo de espera, lo que podría no ser una mala idea. –
'timeout' no está destinado a la ejecución periódica. Se pretende iniciar alguna acción o finalizar cuando no ocurre nada en este período. Este tiempo de espera finaliza con cada acción incluso con algunas acciones del sistema ('sys',' proc_lib', ...). En breve, se desaconseja utilizar el tiempo de espera, excepto algunas cosas de "mantenimiento", como la terminación automática o las limpiezas. –
También está el módulo de timer
, que podría ser utilizado.
http://erldocs.com/R14B02/stdlib/timer.html?i=8&search=timer#cancel_timer/1
Para controlar con precisión el temporizador, es posible que desee utilizar erlang:start_timer
, y guardar cada referencia temporizador que ha creado.
erlang:start_timer
tiene una pequeña diferencia con erlang:send_after
, ver http://www.erlang.org/doc/man/erlang.html#start_timer-3 y http://www.erlang.org/doc/man/erlang.html#send_after-3
Ejemplo de caso de uso:
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,
...
Eso es genial! Es exactamente lo que estoy haciendo en este momento. ¡Gracias! –
¿No se va a desincronizar eventualmente, porque el temporizador dispara cada 6000 + ms? –
@PatrickOscity Claro, podría. Si quiere estar realmente seguro, puede calcular un horario futuro para programarlo y calcular la cantidad exacta de milisegundos para enviar después. –