c# callback timer reentrancy

c# - Temporizador de parada en su método de devolución de llamada



callback timer (5)

He tenido una situación similar con un System.Timers.Timer, donde el evento transcurrido se ejecuta desde un conjunto de subprocesos y debe ser reentrante.

Utilicé este método para solucionar el problema:

private void tmr_Elapsed(object sender, EventArgs e) { tmr.Enabled = false; // Do Stuff tmr.Enabled = true; }

Dependiendo de lo que estés haciendo, es posible que desees considerar un System.Timers.Timer, aquí hay un buen resumen de MSDN

System.Windows.Forms System.Timers System.Threading Timer event runs on what thread? UI thread UI or worker thread Worker thread Instances are thread safe? No Yes No Familiar/intuitive object model? Yes Yes No Requires Windows Forms? Yes No No Metronome-quality beat? No Yes* Yes* Timer event supports state object? No No Yes Initial timer event can be scheduled? No No Yes Class supports inheritance? Yes Yes No * Depending on the availability of system resources (for example, worker threads)

Tengo un System.Threading.Timer que llama a su controlador de eventos apropiado (callback) cada 10 ms . El método en sí no es reentrante y algunas veces puede tomar más de 10 ms . Por lo tanto, quiero detener el temporizador durante la ejecución del método.

Código:

private Timer _creatorTimer; // BackgroundWorker''s work private void CreatorWork(object sender, DoWorkEventArgs e) { _creatorTimer = new Timer(CreatorLoop, null, 0, 10); // some other code that worker is doing while the timer is active // ... // ... } private void CreatorLoop(object state) { // Stop timer (prevent reentering) _creatorTimer.Change(Timeout.Infinite, 0); /* ... Work here */ // Reenable timer _creatorTimer.Change(10, 0); }

MSDN indica que se llama al método de devolución de llamada (cada vez que se dispara el temporizador) en un subproceso separado del grupo de subprocesos. Eso significa que si detengo el temporizador, lo primero en el método aún no evita necesariamente que el temporizador se dispare y ejecute otra instancia del método antes de que el primero tenga la oportunidad de detener el temporizador.

¿Debería tal vez el temporizador (o incluso el método no reentrante) estar bloqueado? ¿Cuál es la forma correcta de evitar que el temporizador se dispare durante la ejecución de su método de devolución de llamada (y no reentrante)?


Lo hago con Interlocked que proporciona operaciones atómicas, y con CompareExchange se asegura que solo un hilo a la vez entre en la sección crítica:

private int syncPoint = 0; private void Loop() { int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0); //ensures that only one timer set the syncPoint to 1 from 0 if (sync == 0) { try { ... } catch (Exception pE) { ... } syncPoint = 0; } }


Podría dejar que el temporizador continúe activando el método de devolución de llamada, pero envuelva su código no reentrante en un Monitor.TryEnter / Exit. No es necesario detener / reiniciar el temporizador en ese caso; Las llamadas superpuestas no adquirirán el bloqueo y volverán inmediatamente.

private void CreatorLoop(object state) { if (Monitor.TryEnter(lockObject)) { try { // Work here } finally { Monitor.Exit(lockObject); } } }


Un par de posibles soluciones:

  • haga el trabajo real en otro delegado de hilos que está esperando un evento. La devolución de llamada del temporizador simplemente señala el evento. El subproceso de trabajo no se puede volver a ingresar, ya que es un solo subproceso que hace su trabajo solo cuando se señala el evento. El temporizador es reentrante, ya que todo lo que hace es señalar el evento (parece una pequeña rotonda y un desperdicio, pero funcionará)
  • ha creado el temporizador con solo un tiempo de espera de inicio y sin tiempo de espera periódico, por lo que solo se activará una vez. La devolución de llamada del temporizador eliminará ese objeto temporizador y creará uno nuevo cuando haya completado su trabajo que también se disparará una sola vez.

Es posible que pueda administrar la opción # 2 sin disponer / crear un nuevo objeto utilizando el método Change() del objeto del temporizador original, pero no estoy seguro de cuál es el comportamiento exacto de llamar a Change() con un nuevo tiempo de espera de inicio después del primer tiempo de espera ha expirado. Eso valdría una prueba o dos.

Editar:

Hice la prueba: manipular el temporizador como un disparo de reinicio parece funcionar perfectamente, y es mucho más sencillo que los otros métodos. Aquí hay un código de ejemplo basado en el suyo como punto de partida (algunos detalles pueden haber cambiado para compilarlo en mi máquina):

private Timer _creatorTimer; // BackgroundWorker''s work private void CreatorWork(object sender, EventArgs e) { // note: there''s only a start timeout, and no repeat timeout // so this will fire only once _creatorTimer = new Timer(CreatorLoop, null, 1000, Timeout.Infinite); // some other code that worker is doing while the timer is active // ... // ... } private void CreatorLoop(object state) { Console.WriteLine( "In CreatorLoop..."); /* ... Work here */ Thread.Sleep( 3000); // Reenable timer Console.WriteLine( "Exiting..."); // now we reset the timer''s start time, so it''ll fire again // there''s no chance of reentrancy, except for actually // exiting the method (and there''s no danger even if that // happens because it''s safe at this point). _creatorTimer.Change(1000, Timeout.Infinite); }


//using Timer with callback on System.Threading namespace // Timer(TimerCallback callback, object state, int dueTime, int period); // TimerCallback: delegate to callback on timer lapse // state: an object containig information for the callback // dueTime: time delay before callback is invoked; in milliseconds; 0 immediate // period: interval between invocation of callback; System.Threading.Timeout.Infinity to disable // EXCEPTIONS: // ArgumentOutOfRangeException: negative duration or period // ArgumentNullException: callback parameter is null public class Program { public void Main() { var te = new TimerExample(1000, 2000, 2); } } public class TimerExample { public TimerExample(int delayTime, int intervalTime, int treshold) { this.DelayTime = delayTime; this.IntervalTime = intervalTime; this.Treshold = treshold; this.Timer = new Timer(this.TimerCallbackWorker, new StateInfo(), delayTime, intervalTime); } public int DelayTime { get; set; } public int IntervalTime { get; set; } public Timer Timer { get; set; } public StateInfo SI { get; set; } public int Treshold { get; private set; } public void TimerCallbackWorker(object state) { var si = state as StateInfo; if (si == null) { throw new ArgumentNullException("state"); } si.ExecutionCounter++; if (si.ExecutionCounter > this.Treshold) { this.Timer.Change(Timeout.Infinite, Timeout.Infinite); Console.WriteLine("-Timer stop, execution reached treshold {0}", this.Treshold); } else { Console.WriteLine("{0} lapse, Time {1}", si.ExecutionCounter, si.ToString()); } } public class StateInfo { public int ExecutionCounter { get; set; } public DateTime LastRun { get { return DateTime.Now; } } public override string ToString() { return this.LastRun.ToString(); } } } // Result: // // 1 lapse, Time 2015-02-13 01:28:39 AM // 2 lapse, Time 2015-02-13 01:28:41 AM // -Timer stop, execution reached treshold 2 //