Lightweight Task Scheduling Library for .NET / Silverlight

I’m currently working on VFS, a virtual file system. For running transfers, VFS internally maintains locks that do have expiration time. Accordingly, I found myself in need for a job scheduling mechanism in order to properly release expired locks. I looked around for a few alternatives, but eventually ended up writing my own lightweight version.

What Does it Do?

Basically, the library allows you to create a job, and submit that job to a scheduler, along with a callback action. This callback action is invoked as soon (or every time) the job is due.

Before going into the details, here’s a first code snippet that creates a simple Job that is supposed to run repeatedly (every 1.5 seconds) for a minute. Once the job is created, it is submitted to a Scheduler instance which processes the job and makes sure the submitted callback action is being invoked every time the job is due:

Silverlight

The project provides class libraries for .NET 3.5 and Silverlight 3, along with a Silverlight sample application that shows how to add scheduling functionality to your SL application with just a few lines of code.

While long-term scheduling isn’t probably something you need to do in a Silverlight application, the scheduler simplifies the management of periodic jobs, such as polling a server for updates. Below is a snippet from the Silverlight sample application. This job starts immediately, and runs indefinitely with an interval of 2 seconds:

Jobs

A job is a simple configuration item for the scheduler. There’s two built-in job types: Job and Job<T>. The only difference between the two is that the latter provides a Data property which allows you to attach state information directly to the job and have it delivered back to you when the job runs.

Creating Jobs

Creating a job is pretty easy, and can be configured through a fluent interface (optional, you can also set the properties the traditional way). Jobs may run once, several times, or indefinitely – you can configure intervals, an optional expiration time, or a maximum number of executions. Here’s a few configuration samples:

//run the job every second until we manually cancel it
Job job = new Job();
job.Run.Every.Seconds(1);

The Scheduler Class

Jobs are being submitted to the Scheduler class, which provides a very simple API. Most methods should be pretty self-explanatory, and they are well documented.

Callback Actions

If you add a Job to the Scheduler through one of the SubmitJob overloads, you have to submit a callback action that is being invoked whenever the job runs. Depending on whether you use Job or Job<T>, a different callback delegate may be used:

Cancelling or Pausing a Job

If you want to cancel or pause a job, you can do this either via the Scheduler class, or directly on the Job instance that is submitted to you whenever the job runs. Note that canceling the job is a terminal operation – a canceled job cannot be resumed.

Actually, doing these operations directly via the Job class is the preferred mechanism – if you use the scheduler’s methods, the scheduler needs to lock its internal list and search for the job itself, which costs you processing power.

Exception Handling

An exception that occurs during the execution of a job does not affect the scheduler – all jobs are being executed on worker threads taken from the .NET thread pool. However, in order not to miss any exceptions, you can instruct the Scheduler class to supervise executing jobs, and forward any unhandled exceptions to a single exception handling routine. All you need to do is registering a callback action to the JobExceptionHandler of the Scheduler class:

Performance

The library could surely be tweaked, but it performs pretty well. Running 10000 jobs, each with an interval of 100 ms (that’s around 100 jobs per millisecond) keeps the CPU busy (at around 25% on my machine), but neither eats away your memory nor freezes the PC. Bigger intervals aren’t a problem at all because the scheduler is sleeping most of the time.

Reacting to System Time Change Events

Assume the following:

The current time is 21:00, your job is scheduled to run at 22:00

The user changes the computer’s time to 21:30

Now, depending on your needs, you might want the scheduler to adjust itself based on two strategies:

A: If you want the job to run at a fixed time (22:00), you expect the job to run in 30 minutes. This is the scheduler’s default behavior.

B: If you want the job to run based on relative times, you still expect the job to run in an hour, so the execution time would have to be change to 22:30.

In order not to miss such an event, the scheduler performs a quick self test with a fixed configurable interval from time to time – even if no jobs are up for execution. Per default, the scheduler’s SelfTestInterval property is set to two minutes, but this can be configured should you need more (or less) accurate re-scheduling.

If you want the scheduler to just readjust its internal schedule and keep fixed times (A), you don’t have to do anything. However, if you want the scheduler to reschedule its jobs if a system time change was detected (B), you can do by setting the SystemTimeChangeRescheduling property of the Scheduler class, which takes an enum value of type ReschedulingStrategy:

As you can see, you can choose not to reschedule at all (default), only reschedule the next execution time (the next time the job runs), or even shift the expiration time of your jobs.

Fixed vs. Relative Rescheduling Depending on Jobs

What if you have a few jobs that should run on a fixed time, while others should be rescheduled?

I decided against making the API more complicated by allowing jobs to be individually configured. As an alternative, I’d suggest to just use two scheduler classes with individual configurations. You could even write a wrapper class that just maintains two schedulers, and forwards all job submissions to the correct one. Here’s a quick but working implementation:

Persisting Jobs

Persisting jobs is not part of the library, but could be done by subclassing the Scheduler class. The class gives you protected access to all its internals, including its job list. Job is a very simple component, so it should be fairly easy to store and reload all jobs.

However, from an architectural point of view, I’d probably not persist any jobs at all, but rather recreate them during initialization based on persisted business data. A job does execute in a given context, because of “something”. Let’s assume this “something” is a reminder flag in a calendar application:

User sets a reminder on a calendar entry.

Calendar application updates database record of the calendar entry, then creates a job with the scheduler.

User turns off application – the scheduler and running jobs are being disposed.

User restarts application.

The application retrieves all reminders from the database and schedules jobs for them.

This approach is simple, and clearly defines the responsibilities of the components in your application. It also minimizes dependencies on the scheduling system.

Implementation Notes

Creating a Fluent Interface

The library provides a fluent API that allows you to chain configuration settings:

//job starts at a given start time, repeats until expiration time
Job myJob = new Job();
myJob.Run.From(startTime).Every.Seconds(1.5).Until(expiration);

The fluent API is optional – a job can also be configured via traditional properties setters. However, the corresponding declaration is more verbose:

DateTime, DateTimeOffset, and SystemTime

SystemTime Pattern

With a scheduler, everything is about time. However, time-related code is very hard to test, which is why the scheduler does not directly access the current system time, but makes use of Ayende’s SystemTime. This is an awesome pattern, and I highly recommend using it whenever timestamps play an important role in code.

DateTimeOffset vs. DateTime

You might have noticed that usually, the code works with DateTimeOffset rather then the commonly known DateTime struct. DateTimeOffset is basically an alternative to DateTime, but operates on UTC which makes it an ideal candidate for most time-related scenarios. You can read more about it at MSDN or at the BCL team blog.

However, you can even still use DateTime in your code if you feel more comfortable with it:

Job List, Timer and Pooled Execution

Job List

Internally, the Scheduler class uses a very simple mechanism: All jobs are being cached in a sorted list, which allows the scheduler to easily determine the next execution time. If a new job is being submitted, the scheduler only has to check whether the new job runs before the next scheduled job or not. If yes, the scheduler adjusts its next execution. If no, the job is just added to the end of the list, and a flag is set that reminds the scheduler to reorder the list once the next job was done. This approach has a big advantage: Even if many jobs are being submitted, reordering takes only place once the next job runs.

publicvoid SubmitJob(Job job, Action<Job> callback)
{
//[validation omitted for brevity]
JobContext context = new JobContext(job, callback);
lock(syncRoot)
{
//if this job is going to be the next one, we need to reconfigure//the timer. Do not reschedule if the next execution is imminentif (NextExecution == null || context.NextExecution <= NextExecution.Value)
{
//insert at index 0 –> makes sure the job runs first on next timer event
jobs.Insert(0, context);
//only reschedule if the next execution is not imminentif (NextExecution == null || NextExecution.Value.Subtract(SystemTime.Now())
.TotalMilliseconds > MinJobInterval)
{
//no sorting required, but we need to adjust the timer
Reschedule();
}
}
else
{
//add at end of the list and mark list as unsorted//the job will be sorted and rescheduled on the next run (which is before//this job's execution time)
jobs.Add(context);
isSorted = false;
}
}
}

Timer and Rescheduling

In order to trigger the execution, a single Timer is used to wake up the scheduler once jobs are due. The timer is set to one of those values:

If the scheduler has no jobs at all, the timer is disabled.

If jobs are pending, the next interval is the execution time of the next pending job.

If the self test interval is lower than the execution time of the next job, the scheduler will run an evaluation at this time (which ensures that a changed system time does not cause the scheduler to oversleep).

Thanks for posting the code and tests. Nicely done. I just implemented something very similar for a project I’m working on. Two things I don’t see here are logging or error handling. What if a job fails during execution?

Peter,
Edit: Exception handling implemented and published with version 1.0.2. Thanks for the feedback, Peter 🙂

I’d rather see the responsibility to handle failed jobs within the client code (on the callback method). However, I’m thinking about adding a callback handler for exceptions into the Scheduler class – mainly in order to handle potential issues with a failing ThreadPool (OutOfMemoryException), but I might extend the mechanism in order to report job exceptions as well. Hmm, need to think about this first…

Nice implementation, may I just ask what was it that convinced you to write your own task scheduler – what was it about Quarz.NET that you didn’t you find adequate?
I’we always had at the back of my mind that Quartz.NET could probably cover all my needs, was it perhaps just the API in your case?

Quartz looked like an obvious choice from the specs, but it turned out to be too heavyweight for me. It has maintained compatibility back to .NET 1.1, and also comes with a dependency to Common.Logging that was a bit of a show stopper to me. And Silverlight compatibility is of course out of the question as well.

However, this doesn’t mean that my library is necessarily the better solution for your project: I guess my approach and Quartz just satisfy different requirements: Quartz is quite a beast and brings a lot to the table in one package (including database storage). Mine is a leightweight solution that can be *very* easily integrated and adjusted if needed, and leverages new language features in order to provide a lean API.

Mac,
The scheduler’s internal job collection is not public, because tampering with it might be delicate in terms of performance and/or multithreading.
In order to make the jobs public, you would have to subclass the Scheduler class like this (note that the collection is locked during enumeration, and does not return the internal list itself):

I wrote down a few thoughts in the section “Persisting Jobs”. I would recommend you to initialize your jobs during application initialization based on your individual business logic, and possible reschedule (by creating new jobs) as necessary.
You could even have a sort of “controller instance” that is responsible for scheduling your jobs being invoked by a job itself – in the end, it depends on your individual needs.

I’m inheriting from your classes and building my own payload objects in order to achieve something similar to comment #12 (scheduling from a database).

I don’t want to alter your original source code in case you release new versions. However, there’s one thing missing for me – it’s not possible to override the job execution from within a Scheduler-derived class (I want to fire a log method in my job classes). Would you consider doing the following?

In JobContext.ExecuteAsync remove the if statement and put the same code into a property:

This looks very very handy since I’m planning a “ToDo notifier”.
I’m interested in how the jobs are called by the scheduler. Are you using a Windows Service? Or does it require a program running in the background, providing the scheduler interface? I didn’t find any information on this in this article, so I hope I can get a quick answer from your highness.
Also, awesome everything on everywhere I found in your blog. You are a very valuable man, sir :3

Regarding your question: The scheduler is just a component that internally manages a timer. So there’s no services or anything like that. This means that if you want to use it with your notifier, the notifier needs to run in the background and just register its jobs during startup.

Check the article above – I’ve left some thoughts on this in the “Persisting Jobs” section.

I am scheduling the task using above methods. But when the application runs and jobs again schedules then they executed immediately and not as per the time which I have given at the time of creating jobs.

E.g. If i schedule job every Monday at 8PM
and if application restart then it takes the time which we logged into the system and execute and then next period sets from current Login time.

I’ve been studying the code again for a couple of hours and I can’t see where the scheduler removes cancelled jobs from the Jobs collection, except in UpdateState() which is called from ExecuteAsync() which only ever runs when the job is executed so far as I can tell. What if the job has an execution time in the distant future – don’t we want to get rid of it before then? (the answer I’m sure is yes, so I’m probably missing something). Also in CancelJob() we don’t set IsSorted to false, will this cause problems if the cancelled job is actually Jobs[0]? Reschedule() doesn’t do any sorting it just grabs Jobs[0]; if the list hasn’t been sorted the new Jobs[0] could be any old random job couldn’t it, not necessarily the one due to run next?

Hi Philipp. First of all, thank you for this great work. Now, I have a question about job execution. What happens if I schedule a job to execute every second and job requires ~2 seconds to finish? another instance of job will be created and executed? or scheduler will wait until the job finishes? Thanks

How do you prevent job concurrency? If a critical job is executing every 15 minutes but taking longer than expected; how do you prevent the same job from executing again until the previous one has finished?

I agree that there’s value in removing canceled jobs right away, but a job is a very lightweight object, and the scheduler performs a self-test every 2 minutes to sanitize itself, so there’s no risk of jobs lurking around. This justifies keeping things simple.
However, you nicely spotted the issue with the first evaluated job being canceled – this was a bug that has been fixed. Thanks!

This looks to be exactly what I need.
Not being a true programmer myself, could you tell me if your scheduler would work ok as part of a vb.net project?
Also, post #25 implies there is a newer version available, if so could you point me in the right direction please? The download on this page seems to still be version 1.0.2.

Philipp, this is great stuff!!! this is what is have been looking for… Please tell me if you think this would work with my website. Basically, i need to schedule 4-task: 1. Start, 2. CheckStatus, 3. End @ 4. CheckReaults. these task could be evoked over 3 to 24 days (star to finish) Please see below:

So, I have a website that runs a kind-of auction. I would like to schedule a number of tasks based on the following factors… any help would be greatly appreciated. Thanks.

Philipp, this is great stuff!!! this is what is have been looking for… Please tell me if you think this would work with my website. Basically, i need to schedule 4-task: 1. Start, 2. CheckStatus, 3. End @ 4. CheckReaults. these task could be evoked over 3 to 24 days (star to finish) Please see below:
So, I have a website that runs a kind-of auction. I would like to schedule a number of tasks based on the following factors… any help would be greatly appreciated. Thanks.
1. Auction StartDate = DateTime.Now (or some date in the future)
2. Auction Duration = x days (where “x” is either; 3, 5, 7, 10 or 14)
3. Auction EndDate = StartDate + Duration
4. ClosingPeriod Duration = 7 days (this is a fix number of days)
5. ClosingPeriod StartDate = Auction EndDate
6. Deal Status CheckDate = Auction EndDate – 2 days
7. Total Transaction Period = Auction Duration + ClosingPeriod Duration (from the 1st to 4th task).
The Process is as follows:
TASKS:
First: START Auction
//Do something
Second: Status CheckDate
//Do something
Third: END Auction (Note, auction close at 12 noon in ALL (6) timeZones across in U.S.)
//Do something
START ClosingPeriod
Forth: END ClosingPeriod
//Do something
My website is in ASP.NET Razor, so what are my options. I assume I will need a dedicated server? I am new to asp.net and programming (only 4-weeks) so be as detailed as possible. Thanks.
Rob

@Rob
You might want to look at Workflow Foundation for complex workflows. Other than that, you’d also have to think of persisting your jobs etc. Given that those workflows are the core of your application, I’m not sure this *lightweight* framework is the way to go there…