26

I wanted to start a Windows service to run a function everyday at specific time.

What method i should consider to implement this? Timer or using threads?

Adel Khayata
  • 2,717
  • 10
  • 28
  • 46
Ziyad Ahmad
  • 527
  • 2
  • 6
  • 18

7 Answers7

80

(1) On first start, Set _timer.Interval to the amount of milliseconds between the service start and schedule time. This sample set schedule time to 7:00 a.m. as _scheduleTime = DateTime.Today.AddDays(1).AddHours(7);

(2) On Timer_Elapsed, reset _timer.Interval to 24 hours (in milliseconds) if current interval is not 24 hours.

System.Timers.Timer _timer;
DateTime _scheduleTime; 

public WinService()
{
    InitializeComponent();
    _timer = new System.Timers.Timer();
    _scheduleTime = DateTime.Today.AddDays(1).AddHours(7); // Schedule to run once a day at 7:00 a.m.
}

protected override void OnStart(string[] args)
{           
    // For first time, set amount of seconds between current time and schedule time
    _timer.Enabled = true;
    _timer.Interval = _scheduleTime.Subtract(DateTime.Now).TotalSeconds * 1000;                                          
    _timer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
}

protected void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    // 1. Process Schedule Task
    // ----------------------------------
    // Add code to Process your task here
    // ----------------------------------


    // 2. If tick for the first time, reset next run to every 24 hours
    if (_timer.Interval != 24 * 60 * 60 * 1000)
    {
        _timer.Interval = 24 * 60 * 60 * 1000;
    }  
}

Edit:

Sometimes people want to schedule the service to start at day 0, not tomorrow so they change DateTime.Today.AddDays(0).If they do that and set a time in the past it causes an error setting the Interval with a negative number.

//Test if its a time in the past and protect setting _timer.Interval with a negative number which causes an error.
double tillNextInterval = _scheduleTime.Subtract(DateTime.Now).TotalSeconds * 1000;
if (tillNextInterval < 0) tillNextInterval += new TimeSpan(24, 0, 0).TotalSeconds * 1000;
_timer.Interval = tillNextInterval;
Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
Settapon H
  • 916
  • 7
  • 6
  • 1
    Instead of run timer every minutes, this timer will run once a day. – Settapon H Jan 03 '14 at 22:21
  • 4
    It looks like you provided a nice code example. It would be helpful to add some explanation of the code around it to specifically address the question from the user. – RacerNerd Jan 03 '14 at 22:37
  • 6
    Instead of making this a service, if you want it to run at a specific point in time, you could consider making it a normal console application, and running it with the Windows Task Scheduler – MarceloBarbosa Jun 08 '15 at 19:08
  • 2
    I think if the task takes a long time to execute, it won't run exactly at 7 next time. It will keep growing. – Mukus Aug 30 '17 at 06:28
  • And run at `7:00 a.m to 21:00 pm` interval each day ***Monday To Friday*** ? ***Not eastern-holidays*** – Kiquenet Mar 21 '18 at 17:01
  • Instead of the " if (_timer.Interval != 24 * 60 * 60 * 1000) { _timer.Interval = 24 * 60 * 60 * 1000; } " - could be better to do _timer.Interval = (_scheduleTime - DateTime.Now).TotalMilliseconds that's way, it doesn't matter how long the function took, as long as it's less than 24 hours – C sharper Jan 21 '19 at 13:46
  • Is this task going to run at 7 am if someone restarts the service in the meantime? – Shehani Kalapuge Sep 16 '20 at 08:12
8

Are you sure, you need a service, that runs only one time per day?

Maybe Windows Task Schedule will be better solution?

8

Good answer (I used your code), but one problem with this line:

_timer.Interval = _scheduleTime.Subtract(DateTime.Now).TotalSeconds * 1000;

If DateTime.now is later than scheduleTime, you will go negative and this will generate an exception when assigning to timer.Interval.

I used:

if (DateTime.now > scheduleTime)
    scheduleTime = scheduleTime.AddHours(24);

Then do the subtraction.

Evan
  • 457
  • 4
  • 14
  • in that case you should do while (DateTime.now > scheduleTime) { scheduleTime = scheduleTime.AddHours(24); } it just makes it better if it is more than just 1 day....it resolves any weirdness that may happen – fireshark519 Apr 02 '19 at 12:40
4

Use Windows built in Task Scheduler (http://windows.microsoft.com/en-us/windows7/schedule-a-task) or Quartz.net.

Unless ... you have a service that's doing lots of other processing and needs to be running all the time in which case a Timer might be appropriate.

Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
0
private static double scheduledHour = 10;
private static DateTime scheduledTime;

public WinService()
{
     scheduledTime = DateTime.Today.AddHours(scheduledHour);//setting 10 am of today as scheduled time- service start date
}

private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
      DateTime now = DateTime.Now;
      if (scheduledTime < DateTime.Now)
      {
         TimeSpan span = now - DateTime.Now;
         scheduledTime = scheduledTime.AddMilliseconds(span.Milliseconds).AddDays(1);// this will set scheduled time to 10 am of next day while correcting the milliseconds
         //do the scheduled task here
      }  
}
0

You can do it with a thread and an event; a timer is not necessary.

using System;
using System.ServiceProcess;
using System.Threading;

partial class Service : ServiceBase
{
    Thread Thread;

    readonly AutoResetEvent StopEvent;

    public Service()
    {
        InitializeComponent();

        StopEvent = new AutoResetEvent(initialState: false);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            StopEvent.Dispose();

            components?.Dispose();
        }

        base.Dispose(disposing);
    }

    protected override void OnStart(string[] args)
    {
        Thread = new Thread(ThreadStart);

        Thread.Start(TimeSpan.Parse(args[0]));
    }

    protected override void OnStop()
    {
        if (!StopEvent.Set())
            Environment.FailFast("failed setting stop event");

        Thread.Join();
    }

    void ThreadStart(object parameter)
    {
        while (!StopEvent.WaitOne(Timeout(timeOfDay: (TimeSpan)parameter)))
        {
            // do work here...
        }
    }

    static TimeSpan Timeout(TimeSpan timeOfDay)
    {
        var timeout = timeOfDay - DateTime.Now.TimeOfDay;

        if (timeout < TimeSpan.Zero)
            timeout += TimeSpan.FromDays(1);

        return timeout;
    }
}
drowa
  • 682
  • 5
  • 13
0

if it is one in a day, why you don't use task scheduler? windows service is useful when you want run task many time in minute. so if you want to run a program in a specific time, its better to use task scheduler and set event of task scheduler on a specific time in day. I do many thing with task scheduler and its perfect. you can set rout of program in task scheduler and set interval time to run it. if you want to run a program every 5 min in a day still you can use task scheduler and its better way.