Stopping timer in its callback method

前端 未结 5 915
逝去的感伤
逝去的感伤 2020-12-05 01:23

I have a System.Threading.Timer that calls its appropriate event handler (callback) every 10 ms. The method itself is not reentrant and can

相关标签:
5条回答
  • 2020-12-05 01:29

    You could let the timer continue firing the callback method but wrap your non-reentrant code in a Monitor.TryEnter/Exit. No need to stop/restart the timer in that case; overlapping calls will not acquire the lock and return immediately.

     private void CreatorLoop(object state) 
     {
       if (Monitor.TryEnter(lockObject))
       {
         try
         {
           // Work here
         }
         finally
         {
           Monitor.Exit(lockObject);
         }
       }
     }
    
    0 讨论(0)
  • 2020-12-05 01:38

    I've had similar situation with a System.Timers.Timer, where the elapsed event is executed from a threadpool and needs to be reentrant.

    I used this method to get around the issue:

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

    Depending on what you're doing you may want to consider a System.Timers.Timer, here's a nice summary from 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)            
    
    0 讨论(0)
  • 2020-12-05 01:43
        //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
        // 
    
    0 讨论(0)
  • 2020-12-05 01:47

    A couple possible solutions:

    • have the real work done in yet another thread delegate that's waiting on an event. The timer callback merely signals the event. The worker thread cannot be reentered, as it's a single thread that does its work only when the event is signaled. The timer is reentrant, since all it does is signal the event (seems a little roundabout and wasteful, but it'll work)
    • have the timer created with only a start timeout and no periodic timeout so it'll fire only once. The timer callback will dispose of that timer object and create a new one when it has completed its work that will also only fire once.

    You may be able to manage option #2 without disposing/creating a new object by using the Change() method of the original timer object, but I'm not sure what the behavior is exactly of calling Change() with a new start timeout after the first timeout has expired. That would be worth a test or two.

    Edit:


    I did the test - manipulating the timer as a restartable one-shot seems to work perfectly, and it's much simpler than the other methods. Here's some sample code based on yours as a starting point (a few details may have changed to get it to compile on my machine):

    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);
    }
    
    0 讨论(0)
  • 2020-12-05 01:50

    I do it with Interlocked that provides atomic operations, and by CompareExchange ensures that only one thread at a time enters the critical section:

    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;
            }
    
        }
    
    0 讨论(0)
提交回复
热议问题