2012-01-30 8 views
11

Tengo un List<System.Threading.Timer>. Cada temporizador dispara en un intervalo configurable (predeterminado 10 minutos). Todos invocan el mismo método de devolución de llamada (con un parámetro diferente). El método de devolución de llamada puede tomar varios segundos para completar su trabajo.Espere System.Threading.Timer Callbacks para completar antes de salir del programa

Cuando el programa finaliza, parece que la ejecución del método de devolución de llamada se detiene inmediatamente (¿lo estoy viendo correctamente?).

¿Cómo puedo esperar elegantemente a que se completen los métodos de devolución de llamada en ejecución antes de salir del programa?

Respuesta

16

usted puede disponer todos los temporizadores con el parámetro WaitHandler. Este controlador se indicará solamente cuando se haya completado el método de devolución de llamada (como dice especificación: "El temporizador no está dispuesta hasta que todas las devoluciones de llamada que están programados han completado")

void WaitUntilCompleted(List<Timer> myTimers) 
{ 
    List<WaitHandle> waitHnd = new List<WaitHandle>(); 
    foreach (var timer in myTimers) 
    { 
     WaitHandle h = new AutoResetEvent(false); 
     if(!timer.Dispose(h)) throw new Exception("Timer already disposed."); 
     waitHnd.Add(h); 
    } 
    WaitHandle.WaitAll(waitHnd.ToArray()); 
} 

Editar: @Peter subrayó la importancia del método Dispose valor de retorno Devuelve falso cuando el temporizador ya está dispuesto. Para asegurarnos de que esta solución permanezca confiable, la modifiqué para lanzar una excepción cuando Timer ya se eliminó, ya que no podemos controlar en ese caso cuando finaliza la devolución de llamada, ¡a pesar de que la devolución de llamada aún podría estar ejecutándose!

+0

Acabo de probar esto y @Peter tiene razón: puede obtener un punto muerto si lo elimina varias veces. Su respuesta es la correcta. –

+0

No estoy de acuerdo con la solución de @Peter, lea mi edición. – Tomek

-2

Probablemente no sea posible esperar la salida en la aplicación de la consola.

Por aplicación de Windows Forms:

Puede crear una variable de contador de devolución de llamada se ejecuta estática que se incrementará cada vez que se inicia la devolución de llamada y una disminución en la salida. Por supuesto, deberías usar el bloqueo al hacer esto.

Y luego puede verificar el evento correspondiente y si espera que un contador se convierta en 0 o simplemente cancelar la salida.

+0

de hecho, es ;-) http://msdn.microsoft.com/en-gb/library/system.console.cancelkeypress(v=vs. 80) .aspx – Seb

+0

La solución de Tomek funciona bien en una aplicación de consola. Su solución también funcionaría en una aplicación de consola.La aplicación tendría que sondear el contador esperando que llegue a 0 antes de las salidas principales. –

1

Puede usar ManualResetEvents para bloquear el hilo principal hasta que se completen las operaciones pendientes.

por ejemplo, si desea todos temporizadores para ejecutar al menos una vez, entonces usted podría tener una matriz System.Threading.ManualResetEvent[] con el estado inicial se establece en no señalado

Así que en algún lugar de su código que tendría su configuración del temporizador y está asociado al waithandle asociado.

// in main setup method.. 
int frequencyInMs = 600000; //10 mins 
Timer timer = new Timer(); 
timer.Elapsed += (s, e) => MyExecute(); 
myTimers.Add(timer) 

ManualResetEvent[] _waithandles = new ManualResetEvent[10]; 
_waithandles[0] = new ManualResetEvent(false); 

// Other timers ... 
timer = new Timer(); 
timer.Elapsed += (s, e) => MyOtherExecute(); 
myTimers.Add(timer)   
_waithandles[1] = new ManualResetEvent(false); 
// etc, and so on for all timers 

// then in each method that gets executed by the timer 
// simply set ManualReset event to signalled that will unblock it. 
private void MyExecute() 
{ 
    // do all my logic then when done signal the manual reset event 
    _waithandles[0].Set(); 
} 

// In your main before exiting, this will cause the main thread to wait 
// until all ManualResetEvents are set to signalled 
WaitHandle.WaitAll(_waithandles);  

Si sólo quería esperar para operaciones pendientes para terminar luego simplemente modificar a algo como esto:

_waithandles[0] = new ManualResetEvent(true); // initial state set to non blocking. 

private void MyExecute() 
{ 
    _waithandles[0].Reset(); // set this waithandle to block.. 

    // do all my logic then when done signal the manual reset event 
    _waithandles[0].Set(); 
} 
3

La respuesta aceptada de Tomek es agradable, pero incompleta. Si la función Dispose devuelve false, significa que no hay necesidad de esperar la finalización, ya que el hilo ya está terminado. Si intenta esperar un WaitHandle en tal caso, WaitAll nunca regresará, por lo que se creó una función que congela arbitrariamente su aplicación/subproceso.

Aquí es cómo debe mirar:

void WaitUntilCompleted(List<Timer> myTimers) 
    { 
     List<WaitHandle> waitHnd = new List<WaitHandle>(); 
     foreach (var timer in myTimers) 
     { 
      WaitHandle h = new AutoResetEvent(false); 
      if (timer.Dispose(h)) 
      { 
       waitHnd.Add(h); 
      } 
     } 
     WaitHandle.WaitAll(waitHnd.ToArray()); 
    } 
+1

En desacuerdo, tirar devolverá falso solo si se llama más de una vez en el temporizador. El hecho de que la devolución de llamada del temporizador haya finalizado no afecta a Dispose return value. – Tomek

+0

Debe asegurarse de que la devolución de llamada no se haya activado aún, si se trata de un temporizador de disparo único, ya que terminará esperando por siempre. –

Cuestiones relacionadas