jwcolby
jwcolby at colbyconsulting.com
Tue May 15 09:28:48 CDT 2012
Is anyone using timers to periodically run processes? I am (was hopefully?) having problems with apparent deadlocks and I found this on the MS site: http://msdn.microsoft.com/en-us/library/system.timers.timer.stop.aspx which discusses deadlocks and provides a solution. I took the code and wrapped it in clsTimer making it fully instantiable so that I could use it in many places. The problem of course is that the code running in the timer's thread still has to cleanly return but with luck at least this (deadlock) part is handled. I am sharing my class so that anyone else doing this stuff can look it over, comment and hopefully find any bugs I might have created in implementation. Here is my version of the indicated code. Comments welcome. using System; using System.Timers; using System.Threading; using System.Windows.Forms; namespace projAccuzip2 { class clsTimer : IDisposable { #region Header // Timer. private bool timerBusy = false; private System.Timers.Timer cTimer = new System.Timers.Timer(); // This is the synchronization point that prevents events // from running concurrently, and prevents the main thread // from executing code after the Stop method until any // event handlers are done executing. private int syncPoint = 0; // Count the number of times the event handler is called, // is executed, is skipped, or is called after Stop. private int numEvents = 0; private int numExecuted = 0; private int numSkipped = 0; private int numLate = 0; // Count the number of times the thread that calls Stop // has to wait for an Elapsed event to finish. private int numWaits = 0; public clsTimer() { cTimer.Elapsed += new System.Timers.ElapsedEventHandler(cTimer_Elapsed); } public clsTimer(int TimerInterval) { cTimer.Interval = TimerInterval; cTimer.Elapsed += new System.Timers.ElapsedEventHandler(cTimer_Elapsed); } #endregion #region Properties public int pNumEvents { get { return numEvents; } } public int pNumExecuted { get { return numExecuted; } } public int pNumSkipped { get { return numSkipped; } } public int pNumLate { get { return numLate; } } public bool pTimerBusy { get { return timerBusy; } } public int pTimerInterval { set { cTimer.Interval = value; } } #endregion #region Events public delegate void delTimer(); public event delTimer evTimer; private void cTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { numEvents += 1; // This example assumes that overlapping events can be // discarded. That is, if an Elapsed event is raised before // the previous event is finished processing, the second // event is ignored. // // CompareExchange is used to take control of syncPoint, // and to determine whether the attempt was successful. // CompareExchange attempts to put 1 into syncPoint, but // only if the current value of syncPoint is zero // (specified by the third parameter). If another thread // has set syncPoint to 1, or if the control thread has // set syncPoint to -1, the current event is skipped. // (Normally it would not be necessary to use a local // variable for the return value. A local variable is // used here to determine the reason the event was // skipped.) // int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0); if (sync == 0) { timerBusy = true; // numExecuted += 1; // No other event was executing. // The event handler now raises an event out in code evTimer(); // Release control of syncPoint. syncPoint = 0; timerBusy = false; } else { if (sync == 1) { numSkipped += 1; } else { numLate += 1; } } } #endregion #region Methods public void mTimerStart() { syncPoint = 0; cTimer.Enabled = true; } public void mTimerStop() { // Allow the timer to run for a period of time, and then // stop it. cTimer.Stop(); // The 'counted' flag ensures that if this thread has // to wait for an event to finish, the wait only gets // counted once. bool counted = false; // Ensure that if an event is currently executing, // no further processing is done on this thread until // the event handler is finished. This is accomplished // by using CompareExchange to place -1 in syncPoint, // but only if syncPoint is currently zero (specified // by the third parameter of CompareExchange). // CompareExchange returns the original value that was // in syncPoint. If it was not zero, then there's an // event handler running, and it is necessary to try // again. while (Interlocked.CompareExchange(ref syncPoint, -1, 0) != 0) { Application.DoEvents(); //Let Windows threads process // Give up the rest of this thread's current time // slice. This is a naive algorithm for yielding. Thread.Sleep(1); // Tally a wait, but don't count multiple calls to // Thread.Sleep. if (!counted) { numWaits += 1; counted = true; } } // Any processing done after this point does not conflict // with timer events. This is the purpose of the call to // CompareExchange. If the processing done here would not // cause a problem when run concurrently with timer events, // then there is no need for the extra synchronization. } #endregion #region Dispose private bool disposed = false; //Track whether Dispose has been called. ~clsTimer() { Dispose(true); } public void Close() { Dispose(true); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SupressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } // Implement IDisposable. // Do not make this method virtual. // A derived class should not be able to override this method. public void Dispose() { Dispose(true); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SupressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } // Dispose(bool disposing) executes in two distinct scenarios. // If disposing equals true, the method has been called directly // or indirectly by a user's code. Managed and unmanaged resources // can be disposed. // If disposing equals false, the method has been called by the // runtime from inside the finalizer and you should not reference // other objects. Only unmanaged resources can be disposed. private void Dispose(bool disposing) { if (!this.disposed) // Check to see if Dispose has already been called. { if (disposing) // If disposing equals true, dispose all managed and unmanaged resources. { mTimerStop(); cTimer = null; } } disposed = true; } #endregion } } -- John W. Colby Colby Consulting Reality is what refuses to go away when you do not believe in it