Task and TaskScheduler

For this type of deal, I think a Cron type scheduler would be perfect and simplier and easier to reason about. Something like below.


public static void TestCron()
{
    // Set clock for testing. 6/8/2011 1:00:45pm
    Utility.SetLocalTime(new DateTime(2011, 6, 8, 13, 0, 45));
 
    Cron cron = new Cron();
    cron.Start();
            
    // mth, dow, hr, min
    Task t1On = Task.FromTime(-1, -1, 13, 1, (Task t) =>
        {
            Debug.Print("\nRun zone 1,2,3 on Weds at 1:01pm : " + t.ToString() + "\n");
            // Open zones 1,2,3 here.
        });
            
    Task t1Off = Task.FromTime(-1, -1, -1, -1, (Task t) =>
        {
            Debug.Print("\nTurning off zones 1,2,3 every minute\n");
            // Turn off 1,2,3 zones here.
        });

    Task t2On = Task.FromTime(-1, (int)DayOfWeek.Wednesday, 13, 3, (Task t) =>
        {
            Debug.Print("\nRun zones 4,5,6 on Weds at 1:03pm : " + t.ToString() + "\n");
            // Open zones 1,2,3 here.
        });

    Task t2Off = Task.FromTime(-1, (int)DayOfWeek.Wednesday, 13, 4, (Task t) =>
        {
            Debug.Print("\nTurning off zones 4,5,6 on Weds at 1:04pm\n");
        });
    cron.Add(t1On, t1Off, t2On, t2Off);
}

//
// Cron.cs
// 
using System;
using Microsoft.SPOT;
using System.Collections;
using System.Threading;

namespace FezPandaApp1
{
    public delegate void CronAction(Task t);
    public class Cron
    {
        ArrayList list;
        bool started;

        public Cron()
        {
            list = new ArrayList();
        }

        public void Add(Task task)
        {
            if (task == null) throw new ArgumentNullException("task");
            lock (list)
            {
                list.Add(task);
            }
        }
        
        public void Add(params Task[] tasks)
        {
            if (tasks == null) return;
            lock (list)
            {
                foreach (Task t in tasks)
                {
                    list.Add(t);
                }
            }
        }
        
        public Array CronTasks
        {
            get
            {
                lock(list)
                {
                    Array[] tl = new Array[list.Count];
                    this.list.CopyTo(tl);
                    return tl;
                }
            }
        }

        public bool IsStarted
        {
            get { return this.started; }
        }

        public void Start()
        {
            lock (list)
            {
                if (started) return;
                started = true;
                new Thread(Loop).Start();
            }
        }

        public void Stop()
        {
            lock (list)
            {
                this.started = false;
            }
        }

        private void Loop()
        {
            Debug.Print("\nCron scheduler started at: " + DateTime.Now.ToString());
            while (started)
            {
                // Sleep till top of next minute.
                int msToTopOfNext = MSToNextMinute() + 1000; // Add a seconds to make sure we are passed top of next minute.
                Debug.Print("Cron waiting for ms: " + msToTopOfNext);
                Thread.Sleep(msToTopOfNext);

                // Check list of tasks to start. Each started on a new thread.
                DateTime now = DateTime.Now;
                Debug.Print("Cron checking tasks at: " + now + " ms:" + now.Millisecond );
                int count = 0;

                foreach(Task t in list)
                {
                    if (!RunNow(t))
                        continue;
                    count++;

                    // Run task.
                    Thread worker = new Thread(()=>
                    {
                        try
                        {
                            t.Action(t);
                        }
                        catch (Exception ex)
                        {
                            t.LastError = ex;
                        }
                    });
                    worker.Start();
                }
                Debug.Print("Cron jobs started this minute: " + count);
            }
            Debug.Print("Cron scheduler thread stopped.");
        }
        
        private bool RunNow(Task t)
        {
            DateTime now = DateTime.Now;
            if ( (now.Month == t.Month || t.Month == -1)                    // -1 matches any month.
                && ((int)now.DayOfWeek == t.DayOfWeek || t.DayOfWeek == -1) // -1 matches any DOW.
                && (now.Hour == t.Hour || t.Hour == -1)                     // -1 matches any hour.
                && (now.Minute == t.Minute || t.Minute == -1 ) )            // -1 match any minute.         
                return true;
            else
                return false;
        }

        private int MSToNextMinute()
        {
            DateTime now = DateTime.Now;
            DateTime thisMin = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0);
            DateTime next = thisMin.AddMinutes(1);
            long ticksToNext = (next - now).Ticks;
            int msToNext = (int)(ticksToNext / TimeSpan.TicksPerMillisecond);
            return msToNext;
        }
    }

    public class Task
    {
        int id;
        private static int counter = 0;
        private CronAction action;
        int month;
        int dayOfWeek;
        int hour;
        int minute;
        Exception error;

        public Task(int month, int dayOfWeek, int hour, int minute, CronAction action)
        {
            if (action == null) throw new ArgumentNullException("action");
            if (month < -1 || month > 12) throw new ArgumentOutOfRangeException("month");
            if (dayOfWeek < -1 || dayOfWeek > 6) throw new ArgumentOutOfRangeException("dayOfWeek");
            if (hour < -1 || hour > 23) throw new ArgumentOutOfRangeException("hour");
            if (minute < -1 || minute > 59) throw new ArgumentOutOfRangeException("minute");
            this.id = Interlocked.Increment(ref counter);
            this.month = month;
            this.dayOfWeek = dayOfWeek;
            this.hour = hour;
            this.minute = minute;
            this.action = action;
        }
        
        public static Task FromTime(int month, int dayOfWeek, int hour, int minute, CronAction action)
        {
            return new Task(month, dayOfWeek, hour, minute, action);
        }

        public int ID
        {
            get { return this.id; }
        }

        public int Month
        {
            get { return this.month; }
        }

        public int DayOfWeek
        {
            get { return this.dayOfWeek; }
        }

        public int Hour
        {
            get { return this.hour; }
        }

        public int Minute
        {
            get { return this.minute; }
        }

        public CronAction Action
        {
            get { return action; }
        }

        public Exception LastError
        {
            get { return this.error; }
            internal set { this.error = value; }
        }
        public override string ToString()
        {
            return "ID: " + this.id + " Month: " + this.month + " DOW: " + this.dayOfWeek + " Hour: " + this.hour + " Min: " + this.minute;
        }
    }
}

BTW - I don’t think I called enumerator for blocking q, so you should be able comment it out, hit F5 and see what depends on it. Comment those. If nothing outside of BlockingQueue is calling that method, then should be fine. But not sure it is right for the task as I think the cron deal fits better. Store your cron cmds in a text file with a job per line.

// Run job1 each day, at 1pm
-1,-1,13,0,MyJob1

// Run job2 each day at 3pm
-1,-1,15,0,MyJob2

// Run job3 every minute on Mondays
-1,1,-1,-1,MyJob3

Excellent. Thx William. I Will try this code today.

thx again.