[Working] Windows Service Task Scheduler c#

What is Scheduler

A scheduler responsibility is to execute several tasks automatically at a particular time. For an example, we will take a case of a web hosting company. In that company, customers must get a mail if their hosting is expiring soon. If there are a lot of customers registered and using their platform, it is very difficult to send the e-mail notification manually. So in that type of cases we are using these schedulers.

Here I'm going to explain a scheduler that runs on daily at a particular time, and another one that runs repeatedly each and every 5 minutes.

Full tutorial to configure scheduler in .net c# to run scheduled jobs
Schedule Jobs in .net c#


How to configure the scheduler

I have included full source code of windows service I had created download below. Before downloading that we will see some details of how it is configured. Timer is used to built this job scheduler. I have created a common Scheduler class by implementing Timer and IDisposable interfaces to schedule tasks to run on a regular intervals or on specific time on everyday. Code is given below


    /// <summary>
    /// Used to schedule tasks to run on regular intervals or on specific time of everyday.
    /// </summary>
    public class Scheduler : Timer, IDisposable
    {
        private TimeSpan _Time { get; set; }
        private ScheduleKind _Kind { get; set; }

        private Scheduler() { }

        /// <summary>
        /// Returns a new instance of timer and starts it
        /// </summary>
        /// <param name="Time">Interval time to execute the timer</param>
        /// <param name="Trigger">Function to call in a new Thread when timer elapses, signal time is given as input parameter to the function</param>
        /// <param name="Kind">Specify scheduler type</param>
        /// <returns>A System.Timers.Timer Instance</returns>
        public static Scheduler StartNew(TimeSpan Time, Action<DateTime> Trigger, ScheduleKind Kind = ScheduleKind.IntervalBased)
        {
            var timer = MakeTimer(Time, Kind);
            timer.Elapsed += (s, e) =>
            {
                try
                {
                    Task.Factory.StartNew(() => Trigger(e.SignalTime));
                }
                catch (Exception ex) { ex.WriteLog(); }
            };
            return timer;
        }

        /// <summary>
        /// Returns a new instance of timer and starts it
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Time">Interval time to execute the timer</param>
        /// <param name="Trigger">Function to call in a new Thread when timer elapses, signal time is given as input parameter to the function</param>
        /// <param name="Kind">Specify scheduler type</param>
        /// <param name="CallBackFn">Optional call back Action to execute after completing the Trigger function. Input parameter of this Action is the result of Trigger function</param>
        /// <returns>A System.Timers.Timer Instance</returns>
        public static Scheduler StartNew<T>(TimeSpan Time, Func<DateTime, T> Trigger, ScheduleKind Kind = ScheduleKind.IntervalBased, Action<T> CallBackFn = null)
        {
            var timer = MakeTimer(Time, Kind);
            timer.Elapsed += (s, e) =>
            {
                try
                {
                    var task = Task.Factory.StartNew<T>(() => Trigger(e.SignalTime));
                    if (CallBackFn != null)
                    {
                        task.ContinueWith((prevTask) => CallBackFn(prevTask.Result));
                    }
                }
                catch (Exception ex) { ex.WriteLog(); }
            };
            return timer;
        }

        /// <summary>
        /// Returns a new instance of timer and starts it
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Time">Interval time to execute the timer</param>
        /// <param name="Trigger">Function to call in a new Thread when timer elapses, signal time is given as input parameter to the function</param>
        /// <param name="Kind">Specify scheduler type</param>
        /// <param name="CallBackFn">Optional call back function to execute after completing the Trigger function. Input parameter of this function is the result of Trigger function</param>
        /// <returns>A System.Timers.Timer Instance</returns>
        public static Scheduler StartNew<T, V>(TimeSpan Time, Func<DateTime, T> Trigger, ScheduleKind Kind = ScheduleKind.IntervalBased, Func<T, V> CallBackFn = null)
        {
            var timer = MakeTimer(Time, Kind);
            timer.Elapsed += (s, e) =>
            {
                try
                {
                    var task = Task.Factory.StartNew<T>(() => Trigger(e.SignalTime));
                    if (CallBackFn != null)
                    {
                        task.ContinueWith((prevTask) => CallBackFn(prevTask.Result));
                    }
                }
                catch (Exception ex) { ex.WriteLog(); }
            };
            return timer;
        }

        /// <summary>
        /// Resets and restarts the scheduler
        /// </summary>
        public void Reset()
        {
            Stop();
            switch (_Kind)
            {
                case ScheduleKind.IntervalBased:
                    Interval = _Time.TotalMilliseconds;
                    break;
                case ScheduleKind.TimeBased:
                    Interval = GetTimeDiff();
                    break;
            }
            Enabled = true;
            Start();
        }

        /// <summary>
        /// Creates a timer
        /// </summary>
        /// <param name="Time"></param>
        /// <param name="Kind"></param>
        /// <returns></returns>
        private static Scheduler MakeTimer(TimeSpan Time, ScheduleKind Kind = ScheduleKind.IntervalBased)
        {
            var timer = new Scheduler { _Time = Time, _Kind = Kind };
            timer.Reset();
            return timer;
        }

        /// <summary>
        /// Returns timespan difference of the next sync time relative to current time
        /// </summary>
        /// <returns></returns>
        private double GetTimeDiff()
        {
            try
            {
                // Sync time of today
                DateTime syncOn = DateTime.Today.AddHours(_Time.Hours).AddMinutes(_Time.Minutes).AddSeconds(_Time.Seconds);
                if (syncOn < DateTime.Now) // If sync time is already past, set to next day
                {
                    syncOn = syncOn.AddDays(1);
                }
                return (syncOn - DateTime.Now).TotalMilliseconds;
            }
            catch (Exception ex) { ex.WriteLog(); return 24 * 60 * 60 * 1000; }
        }

        /// <summary>
        /// Stop and dispose the timer
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            Stop();
            base.Dispose(disposing);
        }
    }

    /// <summary>
    /// Type of scheduler
    /// </summary>
    public enum ScheduleKind : int
    {
        /// <summary>
        /// Scheduler which triggers at a specific time interval
        /// </summary>
        IntervalBased = 0,
        /// <summary>
        /// Scheduler which runs at a specific time of everyday.
        /// </summary>
        TimeBased = 1
    }

and the MasterSyncService partial class that implements the ServiceBase class is given below


    public partial class MasterSyncService : ServiceBase
    {
        System.Timers.Timer AutoMailTimer;
        Scheduler matterSyncScheduler = null;

        // Default 1 day interval
        TimeSpan AutoMailServiceInterval = new TimeSpan(1, 0, 0, 0);
        // Default Sync on 2AM
        TimeSpan AutoMailOnTime = new TimeSpan(2, 0, 0);

        public MasterSyncService()
        {
            InitializeComponent();
            // Interval_days:Hour_in_24_hour_format:Minut:Second
            string intervalConfig = ConfigurationManager.AppSettings["CheckInterval"] ?? "1:0:0:0";
            int[] intervalDetails = intervalConfig.Split(':').Select(f => int.Parse(f)).ToArray();
            if (intervalDetails.Length == 4)
            {
                AutoMailServiceInterval = new TimeSpan(intervalDetails[0], intervalDetails[1], intervalDetails[2], intervalDetails[3]);
            }
            //Helper.WriteLog("MasterServiceInterval is " + MasterServiceInterval);

            // Hour_in_24_hour_format:Minut:Second
            string timeConfig = ConfigurationManager.AppSettings["CheckTime"] ?? "2:0:0";
            int[] timeDetails = timeConfig.Split(':').Select(f => int.Parse(f)).ToArray();
            if (timeDetails.Length == 3)
            {
                AutoMailOnTime = new TimeSpan(timeDetails[0], timeDetails[1], timeDetails[2]);
            }

            AutoMailTimer = new System.Timers.Timer(GetTimeDiff());

            AutoMailTimer.Elapsed += MasterSyncTimer_Elapsed;
            CanPauseAndContinue = true;

            // Start a scheduler which runs on every 10 minutes
            matterSyncScheduler = Scheduler.StartNew(TimeSpan.FromMinutes(double.Parse(ConfigurationManager.AppSettings["RepeatScheduleInterval"] ?? "10")), Repeat);

        }

        /// <summary>
        /// Returns timespan difference of the next sync time relative to current time
        /// </summary>
        /// <returns></returns>
        private double GetTimeDiff()
        {
            try
            {
                // Sync time of today
                DateTime syncOn = DateTime.Today.AddHours(AutoMailOnTime.Hours).AddMinutes(AutoMailOnTime.Minutes).AddSeconds(AutoMailOnTime.Seconds);
                if (syncOn < DateTime.Now)
                {
                    syncOn = syncOn.AddMinutes(1);
                }
                if (syncOn < DateTime.Now) // If sync time is already past, set to next day
                {
                    syncOn = syncOn.AddDays(1);
                }
                Helper.WriteLog(DateTime.Now + ": next schedule on " + syncOn + "\n Interval : " + (syncOn - DateTime.Now).TotalMilliseconds + "ms\n");
                return (syncOn - DateTime.Now).TotalMilliseconds;
            }
            catch (Exception ex) { ex.WriteLog(); return 24 * 60 * 60 * 1000; }
        }

        /// <summary>
        /// Start synchrinizing on timer elapsed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MasterSyncTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                //Helper.WriteLog("Timer Elapsed");
                if (AutoMailTimer.Interval != AutoMailServiceInterval.TotalMilliseconds)
                    AutoMailTimer.Interval = AutoMailServiceInterval.TotalMilliseconds;
                AutoMailTimer.Enabled = true;

                //Helper.WriteLog("Starting sync thread");
                Thread t = new Thread(() => OneTime());
                t.IsBackground = true;
                t.Start();
            }
            catch (Exception ex)
            {
                ex.WriteLog();
            }
        }

        protected override void OnStart(string[] args)
        {
            AutoMailTimer.Interval = GetTimeDiff();
            AutoMailTimer.Start();
            base.OnStart(args);
            matterSyncScheduler.Reset();
        }

        protected override void OnStop()
        {
            //DialogResult result = MessageBox.Show("Please note that if you stop the service, franchisee's classifieds ads will be blocked. Are you sure to disable the service?", "Warning", MessageBoxButtons.YesNo);
            //if (result == DialogResult.Yes)
            //{
            AutoMailTimer.Stop();
            matterSyncScheduler.Stop();
            base.OnStop();
            //}
        }

        protected override void OnContinue()
        {
            AutoMailTimer.Interval = GetTimeDiff();
            AutoMailTimer.Start();
            base.OnContinue();
            matterSyncScheduler.Reset();
        }

        protected override void OnPause()
        {
            //DialogResult result = MessageBox.Show("Please note that if you stop the service, franchisee's classifieds ads will be blocked. Are you sure to disable the service?", "Warning", MessageBoxButtons.YesNo);
            //if (result == DialogResult.Yes)
            //{
            AutoMailTimer.Stop();
            matterSyncScheduler.Stop();
            base.OnPause();
            //}
        }

        /// <summary>
        /// Repeat jobs
        /// </summary>
        /// <param name="syncStartOn">Sync start on date as DateTime.</param>
        internal void Repeat(DateTime syncStartOn)
        {
            try
            {
                //methods executes repeatedly 
            }
            catch (Exception ex)
            {
                ex.WriteLog();
            }
        }


        internal void OneTime()
        {
            try
            {
                //methods executes once a day
            }
            catch (Exception ex)
            {
                ex.WriteLog();
            }
        }
    }

In the above code there are three configuration settings configured in the App.config file, which are

  1. CheckInterval :- Sets how many days, by default it is one, if you want to check by two days, just change the value in App.config file and rebuild the solution. It is settings once in how many days (for example if once in a week, set 7:0:0:0 in the App.config file).
  2. CheckTime :- Sets which time is the once? It is 24-hour time format. For an example, if you want to schedule any job once a day at 14:00 'O clock, then set 14:0:0 in the App.config file.
  3. RepeatScheduleInterval :- Repeated tasks that running on a single day interval settings is done here. For an example, if you want to perform some operations performed in every 10 minutes repeatedly. Just update the value in App.config to 10.
AppSettings are given below


  <appSettings>
    <add key="CheckInterval" value="1:0:0:0"/>
    <add key="CheckTime" value="2:0:0"/>
    <add key="RepeatScheduleInterval" value="5"/>
  </appSettings>

In the above example repeated tasks will run on every 5 minutes and another scheduler will runs once in a day at morning 02:00 'O clock.

Click here to download full source code
[Working] Windows Service Task Scheduler c# [Working] Windows Service Task Scheduler c# Reviewed by TechDoubts on 5:47 AM Rating: 5

No comments:

Powered by Blogger.