Search code examples
c#timerthread-safetysystem.timers.timer

C# - How to pause application until timer is finished?


I have an application that I need to have wait a specific amount of time, but I also need to be able to cancel the current operation if needed. I have the following code:

private void waitTimer(int days)
{
    TimeSpan waitTime = TimeSpan.FromDays(days);
    System.Timers.Timer timer = new System.Timers.Timer(waitTime.TotalMilliseconds);   // Wait for some number of milliseconds
    timer.Enabled = true;
    timer.Start();
    timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); // Subscribe to event handler

    while (!TimerSettings.TimerFinished && !quitToken.IsCancellationRequested);  // Loop forever untill timer is finished or operation is cancled. 

    timer.Elapsed -= new ElapsedEventHandler(OnTimedEvent); // Unsubscribe

    DoWork(); // Do work when timer finishes.......
}

Below is the event handler for the timer finished event:

private void OnTimedEvent(object obj, ElapsedEventArgs e)
{
    TimerSettings.TimerFinished = true;
}

The while loop just loops infinitely until the timer is finished or until a cancelation request is put in. I want to retain this functionality but I would rather not loop forever while waiting for the timer to finish. My timer can be set to run on an interval of multiple days so it doesn't make sense to loop for so long.

Is there another way of doing this?

I know I could do:

Thread.Sleep(runDuration.TotalMilliseconds);

However, this would be blocking and I would not be able to put in a cancelation request.

EDIT: So in order to elaborate on what/why I need to pause here is a more detailed explination of my application. Basically I want to have an application that performs "work" on a regular interval. So based on one of the answers provided below, if I did something like this:

class Program
{
    // Do something in this method forever on a regular interval 
    //(could be every 5min or maybe every 5days, it's up to the user)
    static void Main(string[] args)
    {
        while(true)
        {
          if(args?.Length > 0)
              waitTimer(args[0]);
          else 
              wiatTimer(TimeSpan.FromDays(1).TotalSeconds); // Default to one day interval
        }             
    }

private void waitTimer(int numIntervals)
{
    this.ElapsedIntervals = 0;
    this.IntervalsRequired = numIntervals;
    this.timer = new System.Timers.Timer(1000);   // raise the elapsed event every second
    timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); // Subscribe to event handler
    //timer.Enabled = true; timer.Start() does this for you, don't do this
    timer.Start();
    //thats all here
}

 private void OnTimedEvent(object obj, ElapsedEventArgs e)
 {
    this.ElapsedIntervals += 1;
    if(this.CancelRequested)
    {
       this.ElapsedIntervals = 0;
       this.timer.Stop();
       return;
    }
    if(this.ElapsedIntervals >= this.IntervalsRequired)
    {
       this.ElapsedIntervals = 0;
       this.timer.Stop();
       DoWork();   // This is where my work gets done.......
      return;
    }
  }
}

Then my service/console app would start and go into an infinite loop that just sets timers all day long. Previously, I was actually halting execution of any other code at:

while (!TimerSettings.TimerFinished && !quitToken.IsCancellationRequested);

Which at least worked, but as mentioned, can possibly be resource intensive way to pause a thread. Basically what I really need is a way to block my thread untill the timer is up.

EDIT2: This is my final implementation that seems to work for me using a wait handle...

class TimerClass
{
    /// <summary>
    /// Initialize new timer. To set timer duration,
    /// either set the "IntervalMinutes" app config 
    /// parameter, or pass in the duration timespan.
    /// </summary>
    /// <param name="time"></param>
    internal bool StartTimer(CancellationToken quitToken, TimeSpan? duration = null)
    {
        TimeSpan runDuration = new TimeSpan();
        runDuration = duration == null ? GetTimerSpan() : default(TimeSpan);

        if (runDuration != default(TimeSpan))
        {
            WaitTimer(runDuration); // Waits for the runduration to pass
        }
        return true;
    }

    /// <summary>
    /// Get duration to run the timer for.
    /// </summary>
    internal TimeSpan GetTimerSpan()
    {
        TimerSettings.Mode = App.Settings.Mode;
        DateTime scheduledTime = new DateTime();

        switch (TimerSettings.Mode)
        {
            case "Daily":
                scheduledTime = DateTime.ParseExact(App.Settings.ScheduledTime, "HH:mm:ss", CultureInfo.InvariantCulture);
                if (scheduledTime > DateTime.Now)
                    TimerSettings.TimerInterval = scheduledTime - DateTime.Now;
                else
                    TimerSettings.TimerInterval = (scheduledTime + TimeSpan.FromDays(1)) - DateTime.Now;
                break;
            case "Interval":
                double IntervalMin = double.TryParse(App.Settings.PollingIntervalMinutes, out IntervalMin) ? IntervalMin : 15.00;
                int IntervalSec = Convert.ToInt32(Math.Round(60 * IntervalMin));
                TimeSpan RunInterval = new TimeSpan(0, 0, IntervalSec);
                TimerSettings.TimerInterval = RunInterval;
                break;
            case "Manual":
                TimerSettings.TimerInterval = TimeSpan.FromMilliseconds(0);
                break;
            default:
                TimerSettings.TimerInterval = (DateTime.Today + TimeSpan.FromDays(1)) - DateTime.Now;
                break;
        }
        return TimerSettings.TimerInterval;
    }

    /// <summary>
    /// Event handler for each timer tick.
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="e"></param>
    private void OnTimedEvent(object obj, ElapsedEventArgs e)
    {
        ElapsedIntervals += 1;
        if (CancelRequested.IsCancellationRequested) // If the application was cancled
        {
            ElapsedIntervals = 0;
            timer.Stop();
            WaitHandle.Set();
            return;
        }
        if (ElapsedIntervals >= IntervalsRequired) // If time is up
        {
            ElapsedIntervals = 0;
            timer.Stop();
            WaitHandle.Set();
            return;
        }
    }

    /// <summary>
    /// Timer method to wait for a
    /// specified duration to pass. 
    /// </summary>
    /// <param name="span"></param>
    private void WaitTimer(TimeSpan span)
    {
        WaitHandle = new AutoResetEvent(false);
        int tickDuration = 1000;  // Number of milliseconds for each tick
        IntervalsRequired = Convert.ToInt64(span.TotalMilliseconds / (tickDuration > 0 ? tickDuration : 0.01));
        timer = new System.Timers.Timer(tickDuration);          // Raise the elapsed event every tick
        timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); // Subscribe to event handler for when each tick is complete
        timer.Start();           // Start ticking
        WaitHandle.WaitOne();    // Halt the main thread untill span is reached
    }


    // Timer parameters: 
    private static long ElapsedIntervals { get; set; }
    private static long IntervalsRequired { get; set; }
    private static System.Timers.Timer timer { get; set; }
    private static CancellationToken CancelRequested { get; set; }
    private static string Mode { get; set; }
    private static TimeSpan TimerInterval { get; set; }
    private static EventWaitHandle WaitHandle { get; set; }
}

internal static class TimerSettings
{
    internal static string Mode { get; set; }
    internal static TimeSpan TimerInterval { get; set; }
}

Solution

  • You should look at the Timer.Elapsed Event documentation. This event will be raised repeatedly every time the interval elapses while the AutoReset property is set to true (which is default). I would keep your own count of how many intervals have elapsed and compare it to the required elapsed intervals in this event handler to check whether it is time to stop the timer. In that event, you can also handle cancellation. If your timer finishes its required number of intervals, you may call your doWork function from that event handler.

    private void waitTimer(int numIntervals)
    {
        this.ElapsedIntervals = 0;
        this.IntervalsRequired = numIntervals;
        this.timer = new System.Timers.Timer(1000);   // raise the elapsed event every second
        timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); // Subscribe to event handler
        //timer.Enabled = true; timer.Start() does this for you, don't do this
        timer.Start();
        //thats all here
    }
    
    private void OnTimedEvent(object obj, ElapsedEventArgs e)
    {
        this.ElapsedIntervals += 1;
        if(this.CancelRequested)
        {
            this.ElapsedIntervals = 0;
            this.timer.Stop();
            return;
        }
        if(this.ElapsedIntervals >= this.IntervalsRequired)
        {
           this.ElapsedIntervals = 0;
           this.timer.Stop();
           DoWork();
           return;
        }
    }
    

    https://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed(v=vs.110).aspx

    As I see it, with regards to "pausing", there are two reasons to want to pause and I am unsure which reason is yours:

    1. You want to prevent the application from "finishing" execution and terminating normally.
    2. You want to hold off on executing other code until the number of required intervals has elapsed

    If your reason is #2, then this answer is complete.