2009-04-20 10 views
28

Tengo un proceso que necesita hacer algún trabajo cada quince segundos. Actualmente lo estoy haciendo de esta manera:¿Cuál es la mejor manera de hacer algo periódicamente en Erlang?

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

Esto funciona, pero es extremadamente frágil: si quiero enseñar a mi servidor un mensaje nuevo, cuando escribo cualquier nueva función de controlador, tengo que recuerde incluir el intervalo de tiempo de espera opcional en su valor de retorno. Es decir, si digo que estoy manejar una llamada sincrónica, lo que necesito hacer esto:

 
    %% Someone wants to know our state; tell them 
    handle_call(query_state_data, _From, StateData)-> 
     {reply, StateData, _NewStateData = whatever(), interval_milliseconds()}; 

en lugar de

 
    %% Someone wants to know our state; tell them 
    handle_call(query_state_data, _From, StateData)-> 
     {reply, StateData, _NewStateData = whatever()}; 

Como se puede adivinar, he hecho ese mismo error que un número de veces. Es desagradable, porque una vez que el código maneja ese mensaje query_state_data, los tiempos de espera ya no se generan y todo el servidor se detiene. (Puedo "desfibrilarlo" manualmente obteniendo un shell en la máquina y enviando un mensaje de "tiempo de espera" a mano, pero ... eww.)

Ahora, podría intentar recordar especificar siempre el parámetro Timeout opcional en mi valor de resultado Pero eso no escala: lo olvidaré algún día, y estaré mirando este error una vez más. Entonces, ¿cuál es la mejor manera?

No creo que quiera escribir un bucle real que se ejecute para siempre, y que pase la mayor parte del tiempo durmiendo; eso parece contrario al espíritu de OTP.

Respuesta

19

La mejor manera es:

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

Han pasado un par de años desde que toqué a Erlang, pero esto parece ser lo que terminé haciendo. Gracias. – offby1

7

utilizar el módulo timer :)

+0

no es la mejor solución: http://erlang.org/doc/efficiency_guide/commoncaveats.html#id60206 – mspanc

35

Uso timer: send_interval/2. Por ejemplo:

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

/me golpea la frente Gracias :) – offby1

+1

a menos que necesite tiempos de espera precisos de menos de milisegundos y luego necesite rodar su propia solución –

+19

Elegí usar erlang: send_after, en lugar de timer: send_interval, y pensé que sería esclarecedor explicar por qué. Es porque mi handle_info puede tardar tanto tiempo en completarse que aún se ejecutará cuando llegue el siguiente intervalo, y no quiero que los mensajes de tiempo de espera se acumulen en la cola. Al usar erlang: send_after (una vez en la función init, y una vez más al final de la función handle_info (timeout, ...)), puedo asegurarme de que cada tiempo de espera viene _por lo menos_ interval_milliseconds después del anterior. Esto podría no ser adecuado para todos, pero parece correcto para mí. – offby1

Cuestiones relacionadas