1

I am developping a Windows Service that has to execute a method at a specific exact time.

When the service starts, it extracts several different DateTimes from an SQL Server database and should then start a thread for each DateTime. Each thread should wait until the exact DateTime value, then do some work, then wait again for a shorter period of time and then do some final work again.

I don't want to use System.Timers.Timer because:

  • the method has to be executed at the exact specified time
  • this exact time could be in more than 24h
  • I need to pass a parameter to the Elapsed event

How can I implement this?

Thank you.

EDIT: I just had an idea last night, what if I write a dynamic polling timer? For exemple, if the due datetime is in more than 12 hours it can poll the time every hour to re-synchronize. Then if the due datetime is in more than 3 hours it can poll the time every 30min and so on... What do you think? Would that be a bad code to write?

Thank you again.

gilgha
  • 55
  • 1
  • 7
  • possible duplicate of [How to set timer to execute at specific time in c#](http://stackoverflow.com/questions/21299214/how-to-set-timer-to-execute-at-specific-time-in-c-sharp) – itsme86 Mar 12 '14 at 15:01
  • The only correct answer to this question is "You can not", as due to limitations of the operating system you can not schedule anything down to the exact millisecond. That being said, why do bullet 1 and 2 prevent you from using `System.Timers.Timer`? – Thorsten Dittmar Mar 12 '14 at 15:23

4 Answers4

2

You can't pass a parameter to the tick event with System.Timers.Timer, but you can with System.Threading.Timer. The delay time "greater than 24 hours" is no problem. The timers use a 32 bit period that is expressed in milliseconds. 2^31 milliseconds works out to something like 24.85 days. There's also an overload that lets you specify the delay with a 64-bit signed integer. 2^63 milliseconds is ... several hundred million years.

But having those timers tick at an exact date/time far in the future is problematic because you have to specify the delay time, and a clock change will cause your delay to be too short or too long (think Daylight Saving Time, or user-initiated time changes). You can specify the tick time and have a timer poll the current time every second. It's kind of ugly, but it works.

Another option is to use the Windows Waitable Timer object, which lets you set an exact date and time. Unfortunately, the Framework doesn't include a corresponding object. I wrote an article about it some years ago and published the code. The article is no longer available online, but you can download the code from http://mischel.com/pubs/waitabletimer.zip.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • Thank you for your answer. I agree that having a timer polling the current time every second is kind of ugly but what if I write a dynamic polling timer? For exemple, if the due datetime is in more than 12 hours it can poll the time only every hour. Then if the due datetime is in more than 3 hours it can poll the time only every 30min and so on... – gilgha Mar 13 '14 at 07:46
  • @gilgha: It doesn't really matter if you poll every second, or use some algorithm to adjust the polling frequency. Resource usage really isn't an issue unless your machine is seriously overloaded. Polling is polling. The objections to it in this case are largely aesthetic. – Jim Mischel Mar 13 '14 at 13:47
1

I have used something called ManualResetEvent in the past with a lot of success. Assuming that you know the exact time to run your code then you will be able to calculate estimated time to run (TTR) and subsequent runs should be configured within the first run.

partial class SomeService : ServiceBase
{
    private ManualResetEvent stop = new ManualResetEvent(false);
    private List<DateTime> times;
    private int idxDT = 0;

    public SomeService()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        this.stop.Reset();

        //implement you logic to calculate miliseconds to desired first run time
        int miliseconds_to_run = 1;

        ThreadPool.RegisterWaitForSingleObject(this.stop, 
                                               new WaitOrTimerCallback(ThreadFunc),
                                               null,
                                               miliseconds_to_run, 
                                               true);

    }

    private void ThreadFunc(object _state, bool _timedOut)
    {
        if (_timedOut)
        {
            if(this.times == null)
            {
                //get a list of times to run, store it along with the index of current TTR
                this.times = new List<DateTime>();
            }

            int miliseconds_to_run = (this.times[this.idxDT++] - DateTime.Now).Miliseconds;

            ThreadPool.RegisterWaitForSingleObject(this.stop,
                                                   new WaitOrTimerCallback(ThreadFunc),
                                                   null,
                                                   miliseconds_to_run,
                                                   true);
       }
    }


    protected override void OnStop()
    {
        this.stop.Set();
    }
}

Of course this highly depends how exact your job start time has to be. ThreadPool class will send a Thread alocation request to the OS, and then it will wait for the next available Thread from the Pool. In some processes with lots of lots of threads this could lead to Thread starvation and your exact times will be late.

You could also try creating Task Scheduler Jobs from .NET but I've never done that before.

anusiak
  • 383
  • 2
  • 13
1

Even though you said you don't want to use System.Timers.Timer I'll show you how to do it anyway, as bullet 1 and two in your question don't seem to be valid points against using it in my opinion.

So here's what I do in several applications of mine:

// 1. Create the timer
System.Timers.Timer timer = new System.Timers.Timer();
timer.AutoReset = false;
timer.Elapsed += ...;

// 2. Calculate the number of milliseconds between the scheduled time and the current time
timer.Interval = (scheduledDate - DateTime.Now).TotalMilliSeconds;

// 3. Start the timer
timer.Start();

Done. The event will fire "roughly" at the desired time. Please note that this doesn't work down to the exact millisecond, as Windows is not a real-time OS.

Thorsten Dittmar
  • 55,956
  • 8
  • 91
  • 139
1

I did face sort-off similar issue and used below solution which might solve your issue.

I used ManualResetEvent class, added a Wait method and made the service wait for the exact time.

protected ManualResetEvent waitEvent = new ManualResetEvent(false);

protected bool Wait(int milliSecs)
    {
        return !this.waitEvent.WaitOne(milliSecs, true);
    }

I used this Wait in OnStart method as below:

 protected override void OnStart(string[] args)
    {

        DateTime nextRun = dt;//datetime from the database

        YourProcessOrThreadToRun process = new YourProcessOrThreadToRun();
        while (Wait((int)nextRun.Subtract(DateTime.Now).TotalMilliseconds))
        {
                process.StartProcess();                
        }
    } 

YourProcessOrThreadToRun is the thread you want to run on that exact time.

ganesh
  • 220
  • 3
  • 8