Click here to Skip to main content
Click here to Skip to main content

Time scheduler in C#

, 16 May 2007
Rate this:
Please Sign up or sign in to vote.
This library allows iterating through a sequence of events or time ranges based on a time schedule.

Introduction

The Idaligo.Time library consists of a set of tools which allow iterating through a timeline. The iteration process is based on a schedule that specifies what time pieces to be involved into the iteration.

Background

The .NET framework has strong means to deal with time, but there are no standard classes that help to arrange time scheduled processes. This library is a first attempt to generalize time walking mechanisms using advantages of the C# 2.0 language.

Using the code

The very basic usage of the library is to set up a schedule and start an iterating process based on it.

CronSchedule schedule = new CronSchedule().Hour(14).Hour(22).DayOfMonth(2, 
                        4).DayOfWeek(DayOfWeek.Thursday).Month(2).
                        Month(11).Month(10).Minute(3).Minute(10, 30);

ISequence sequence = Helper.CreateSequence(schedule);
int times = 0;
foreach (DateTime at in sequence.Walk(DateTime.Now))
{
    Trace.WriteLine(at.ToString("f"));
    if (times > 500)
    {
        break;
    }
    times++;
}

As you can see, there is the CronSchedule class which allows to set up a schedule in the way the Unix cron command does. In this example, we are going through all the 3rd and 10th to 30th minutes of every 14th and 22nd hour of the second, third, and forth days of every February, October, and November as well as every Thursday of these months. Time traveling starts from the present time, which is specified as an argument of the Walk(...) method of the time sequence.

Note that the iteration process will never stop unless we terminate it intentionally, or we are out of the range of the DateTime values.

Some times, you may want to iterate through two separate sequences simultaneously. In this case, you are likely to use the following pattern:

DateTime now = new DateTime(2007, 5, 2);
SequenceSet<String> set = new SequenceSet<string>();
set.Add("One", 
    Helper.CreateSequence(new CronSchedule().Minute(13, 20).Hour(12)));
set.Add("Another", 
    Helper.CreateSequence(new CronSchedule().Minute(0, 59).Hour(12)));
int number = 0;
foreach (SequenceSetPoint<string> point in set.Walk(now))
{
    Trace.WriteLine(point.Key + ": " + point.At);
    if (number > 100)
    {
        break;
    }
    number++;
}

In this sample, we are going through two time sequences and getting time events from both of them in the order whichever comes first. In order to find out which sequence has produced an event, we mark them with some arbitrary keys, the only purpose of which is to distinguish one sequence from another.

Sometimes, you might want to iterate not through separate moments of time but through time ranges. If so, try following the next example:

DateTime now = new DateTime(2007, 05, 2);
CronSchedule schedule = new CronSchedule().Hour(14).Hour(22).DayOfMonth(
             2, 4).DayOfWeek(DayOfWeek.Thursday).Month(2).Month(11).
             Month(10).Minute(3).Minute(10, 30);
SuperposedArea area = Helper.CreateSuperposedArea(schedule);

int times = 0;
foreach (TimeRange at in area.Walk(now))
{
    Trace.WriteLine(at.ToString("f"));
    if (times > 500)
    {
        break;
    }
    times++;
}

Every time range you will get in this traverse represents a passage of time which includes the point of beginning but does not include the point at the end. That is, the following holds true:

t is in the time range, if timerange.from <= t < timerange.till

Idaligo.Time.Timer

This is a class that has not been documented intentionally because it is quite hard to fully test it and I cannot guarantee that it works absolutely properly. However, people ask me to show how to use it. Here we go:

[TestFixture]
public class TestTimer
{
    /// <summary>
    /// Any custom class you may want to use, for example
    /// to keep the current state of the process
    /// </summary>
    public class MyMegaClass
    {
        private string name;
        public MyMegaClass(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            this.name = name;
        }
        public override string ToString()
        {
            return name;
        }
    }
    
    private void timer_Fire(object sender, FireEventArgs<MyMegaClass> args)
    {
        Trace.WriteLine(args.Key.ToString() + ": " + 
                        args.At.ToString("f"));
    }

    [Test]
    public void Test()
    {
        DateTime now = DateTime.Now.AddTicks(new 
                       Random(DateTime.Now.Millisecond).Next());
        SequenceSet<MyMegaClass> set = 
                       new SequenceSet<MyMegaClass>();
        set.Add(new MyMegaClass("One"), 
                Helper.CreateSequence(new CronSchedule().Minute(0, 59))); 
        set.Add(new MyMegaClass("Another"), 
                Helper.CreateSequence(new CronSchedule().Minute(0, 59)));

        // in this test we set up the schedule to fire every minute,
        // but in a real life the CronSchedule is likely to look like this
        // new CronSchedule().Hour(14).Hour(22).DayOfMonth(2, 
        //     4).DayOfWeek(DayOfWeek.Thursday).Month(2).Month(11).
        //     Month(10).Minute(3).Minute(10, 30)        

        using (Timer<MyMegaClass> timer = new Timer<MyMegaClass>(set))
        {
            timer.Fire += new FireEventHandler<MyMegaClass>(timer_Fire);
            timer.Start();
            Thread.Sleep(1000 * 10 * 60);
            timer.Stop();
            Thread.Sleep(1000 * 2 * 60);
        }
    }
}

In order to create an instance of the Timer class, we have to create a SequenceSet first and pass it to the constructor. The generic Timer class has to have the same class parameter as a corresponding SequenceSet. The purpose of this class parameter is to be as an indicator that allows to distinguish events from different sequences in the sequence set. In the simplest case, it would be a string or GUID. When you start the timer, you will get events that have this string or GUID or whatever else, just to let you make a decision what to do with this event. In a more complex case, you may want to associate some state information with an event; if so, you have to define your own state class and use it as a class parameter creating both Timer and SequenceSet instances. Later, when you get an event from the timer, you will also get your state object.

Please note!!! My Timer class is based on the standard System.Threading.Timer class which puts a callback method in a separate thread of the ThreadPool each time the event happens. This means that the timer will not wait until the previous event has been processed completely; instead of this, it just starts executing another thread for each next event despite of if the previous one has finished or not. So keep in mind, if your event handler takes too much time to process the current event, the next event might not be processed if the thread pool is out of free threads.

See also

Take a look at my productivity tool for C# coders.

History

As of May 16th, 2007:

  • The last days of the month issue has been fixed.
  • The sequence set with only one sequence issue has been fixed.
  • The first iteration of the sequence issue has been fixed.
  • Timer critical issue with duplicating events has been resolved.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

Aleksey Bykov

United States United States
C# .NET developer since 2003

Comments and Discussions

 
GeneralRe: Design and Flexibility Pinmemberseeblunt11-May-07 18:19 
GeneralRe: Design and Flexibility [modified] Pinmemberseeblunt11-May-07 20:07 
GeneralRe: Design and Flexibility Pinmemberseeblunt16-May-07 4:01 
GeneralRe: Design and Flexibility Pinmembersides_dale19-May-07 9:56 
GeneralStructure of your code PinmemberBillWoodruff5-May-07 0:13 
GeneralRe: Structure of your code PinmemberBillWoodruff16-May-07 14:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 16 May 2007
Article Copyright 2007 by Aleksey Bykov
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid