Wait for System.Threading.Timer Callbacks to Complete before Exiting Program

后端 未结 4 413
抹茶落季
抹茶落季 2021-01-11 15:12

I have a List. Each Timer fires at a configurable interval (default 10 minutes). All call the same callback method (with a diffe

相关标签:
4条回答
  • 2021-01-11 15:40

    It's probably not possible to wait for exit in console application.

    For windows forms application:

    You can create a static running callback counter variable which will be increased each time the callback is started and decreased on exit. Of course, you should use lock when doing this.

    And then you can check the corresponding event and whether wait for a counter to become 0 or just to cancel the exit.

    0 讨论(0)
  • 2021-01-11 15:53

    The accepted answer from Tomek is nice, but incomplete. If the Dispose function returns false, it means that there is no need to wait for completion, as the thread is already finished. If you make an attempt to wait on a WaitHandle in such a case, WaitAll will never return, so you created yourself a function that arbitrarily freezes your application/thread.

    Here is how it should look:

        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());
        }
    
    0 讨论(0)
  • 2021-01-11 15:57

    You can Dispose all timers with WaitHandler parameter. This handler will be signaled only when callback method is completed (as spec says: "The timer is not disposed until all currently queued callbacks have completed.")

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

    Edit: @Peter underlined importance of the Dispose method return value. It returns false when timer already disposed. To make sure this solutions stays reliable, I modified it to throw exception when Timer already disposed as we can't control in such case when its callback finishes, despite earlier disposal callback might still be running!

    0 讨论(0)
  • 2021-01-11 16:03

    You could use ManualResetEvents to block the main thread until any pending operations have completed.

    for example if you would like all timers to execute at least once then you could have an System.Threading.ManualResetEvent[] array with the initial state set to non-signalled

    So somewhere in your code you would have your timer setup and it's associated waithandle initialised.

    // 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);    
    

    If you only wanted to wait for pending operations to finish then simply modify to something like this:

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