0

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; }
}
Hooplator15
  • 1,540
  • 7
  • 31
  • 58
  • You should elaborate on what it is you want to "pause" and why. – Jakotheshadows Jul 07 '16 at 20:30
  • As noted in one answer, a busy loop is _awful_. Maybe slowing it down with a `Thread.Sleep()` would help, but only a little and it's just not necessary. You can do it _right_ without any difficulty. See the marked duplicate. You can either `Wait()` as shown in the accepted answer, or (even better) use an `async` method and `await` the `Task`. – Peter Duniho Jul 08 '16 at 01:29

2 Answers2

0

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.

Jakotheshadows
  • 1,485
  • 2
  • 13
  • 24
  • This is a windows service. I like your timer implementation but my applicaiton isn't pausing. – Hooplator15 Jul 07 '16 at 20:17
  • So what is the reason then that you need to "pause" the application? Wouldn't it be fine to just make your last line of code in the relevant function the call to waitTimer and then finish up the remainder of the work by calling an appropriate DoWork function from the Elapsed event handler? – Jakotheshadows Jul 07 '16 at 20:22
  • I see your point but based on the flow of my application, after the `timer.Start();` line in the waitTimer method, the application will just continue execution. So say I have a process that runs in a loop 10 times. On loop one, I call `waitTimer(10)` . My expected behaviour would be wait ten seconds then doWork(). What actually happens is waitTimer() gets called 10 times back to back in a row and doWork() happens 10 times all at once after the inital 10 seconds passes! I would expect doWork to happen once every 10 seconds. – Hooplator15 Jul 07 '16 at 20:32
  • Another example would be to say, for the sake of example, that this is a ConsoleApplication. What if my console application does only one thing: i.e. a call to waitTimer(10) in the Main method. Wouldn't the app just open, call waitTimer() then immediately close? It would never stay running long enough for the OnTimedEvent() to fire unless, again, I put in some 'bussy-wait' loop or a thread.sleep(). – Hooplator15 Jul 07 '16 at 20:38
  • 1
    I would recommend you use a normal console application and run it as a scheduled task rather than use a timer inside a service. http://weblogs.asp.net/jongalloway/428303 – Jakotheshadows Jul 07 '16 at 20:40
  • I ended up solving this by using a `System.Threading.EventWaitHandle` I just did `EventWaitHandle handle = new AutoResetEvent(false);` Then when the OnTimedEvent fired, I simply set the wait handle: `handle.Set()` which solved my issue. I was able to block the current thread by doing this. – Hooplator15 Jul 07 '16 at 21:30
  • You should update your post / post an answer to reflect that so other people can find out how you solved the problem. – Jakotheshadows Jul 07 '16 at 22:02
  • @Jakotheshadows he should post a separate answer with that other solution, or add it to this answer as another solution (without removing the current solution) – barlop Feb 13 '19 at 14:46
  • Where am supposed to put your code? https://i.imgur.com/gjRDEiX.png – barlop Feb 13 '19 at 15:11
-4

First of all:   "you absolutely do not(!) want to 'busy-wait' for anything!" (BAD Dog! NO Biscuit!)

Ahem...

A practical solution to this problem is to perform a timed wait on a semaphore (or any other suitable mutual-exclusion object ...), instead of using an actual Timer. If you need to bust-out of the wait before it is finished, just strobe the thing upon which it is waiting.

The critical problem with your present "solution" is that it will slam the process into 100% CPU utilization, altogether wastefully. Never do that!

Mike Robinson
  • 8,490
  • 5
  • 28
  • 41
  • 4
    "The critical problem with your present "solution" is that it will slam the process into 100% CPU", yes, but a `Thread.Sleep(100)` or appropriate value in his while loop will fix that (rather than spinning at full speed). Also, it would be nice to see your "practical solution", because despite reading your answer I have no idea how to implement it and would just add a `Sleep` into OP's code instead. – Quantic Jul 07 '16 at 19:39
  • Up-voting your comment because, in the real world, that could very well be sufficient. "Whatever works, *'**good enough** for peace work,'"* as my late uncle used to say. A "100-milisecond sleep" within the otherwise-busy wait-loop would indeed side-step the problem, and I'm *totally* in favor of "whatever works." – Mike Robinson Jul 07 '16 at 19:42
  • I agree with the comment suggesting that this answer isn't very useful without a code example. Frankly, the question's been asked many times already, so I don't really think we _need_ yet another answer on Stack Overflow. But if you're going to provide one, it should be a good one. A timed wait on a `SemaphoreSlim`, `ManualResetEvent`, etc. is not a bad suggestion, though a bit out of date given the current TPL features. – Peter Duniho Jul 08 '16 at 01:31
  • Talking to OP like to a dog? Seriously? Disregarding the fact that busy-waiting has its real-world use-cases, as it has the lowest latency. – Juraj Fiala Feb 22 '22 at 16:26