Introduction
This article will help you build a recurring task scheduler that will execute at a specified interval of time. This article overcomes the complexity of scheduling applications that you may find on this site or on internet. This small and easy to understand code overcomes the limitation of installing a Web Service (with special permissions) or a Windows Service or instantiating any other process to run a scheduler. It is safe to run on a shared hosting provider if coded carefully and with the ability to shut down the task if needed.
Background
First of all, let me give a full credit to Michael Taper at angrycoder.com who first wrote the code (the idea behind how this works) in C# back in April 2003. I'm giving credit to this guy since it appears that he wrote that article. I ran into his code in 2008 with ASP.NET 3.5 around, and I I could not make this code run for me in VB.NET. Also, the original coder description on that site was poor in explaining what the code actually does – so I decided to convert his C# code to VB.NET to run on the current .NET version (2.0 or greater as of March 2008) and to bring it down to a level for not so advanced coders.
Using the code
I've placed the whole code in a single file that is constructed like this:
- The
ISchedulerJob
interface
- Classes that implement the interface above
- Scheduler configuration class
- Scheduler engine class
The ISchedulerJob
interface is only one line of code that will execute each class that implements it.
Public Interface ISchedulerJob
Sub Execute()
End Interface
That is plan simple. The Sub Execute()
line tells each class that implements it that this call must be defined and implemented for this interface. This ensures the Execute
event will fire on each scheduled job. Now, let's get to the second step implementing this interface in our classes. My need for this scheduler was to check the website database and to send an e-mail to users that satisfy a certain criteria. The logic behind this is not included since it goes beyond the scope of this article. As any other scheduler application, this code works by having the jobs created. The job can be anything you wish (anything you can code to execute), but it has to be wrapped in a class that implements the interface above.
The first class, MailJob
, actually represents a single job that will be executed on the schedule:
Public Class MailJob
Implements ISchedulerJob
Public Sub Execute() Implements ISchedulerJob.Execute
MMCUtil.SendMail("tosomeone@domain.com", _
"Subject:testing schedule from mail job ",_
"Body:Body of schedule")
End Sub
End Class
You can see that this class implements the Execute
sub of our Job interface, and all it does is call my SendMail
function.
The next class is just for demonstration purposes. It represents another job that will be executed at the same time as the MailJob
class.
Public Class OtherJob
Implements ISchedulerJob
Public Sub Execute() Implements ISchedulerJob.Execute
MMCUtil.SendMail("tosomeone@domain.com", _
"Subject:testing schedule from other job ", _
"Body:Body of other job schedule")
End Sub
End Class
As you can see, it does exactly the same as the previous job but the subject and body are different, so it technically is a different job to execute.
You can add as many job classes as you like. The job classes do not have to be executed. To include the job in the the execution schedule, each job class has to instantiated. Later on, I will discuss in more detail how to achieve this.
The next step is to build the configuration class. The configuration has two properties: an integer that keeps the time (in milliseconds) that you wish to wait before the scheduled jobs execute, and an ArrayList
that keeps the jobs to be executed.
Public Class SchedulerConfiguration
Private m_sleepInterval As Integer
Private m_jobs As New ArrayList()
Public ReadOnly Property SleepInterval() As Integer
Get
Return m_sleepInterval
End Get
End Property
Public ReadOnly Property Jobs() As ArrayList
Get
Return m_jobs
End Get
End Property
Public Sub New(ByVal newSleepInterval As Integer)
m_sleepInterval = newSleepInterval
End Sub
End Class
The scheduler engine is the one that makes the jobs run. This is where you will wan to pay some attention (design your code carefully), since the jobs are recurring and the execution is placed in an indefinite loop.
Public Class Scheduler
Private configuration As SchedulerConfiguration = Nothing
Public Sub New(ByVal config As SchedulerConfiguration)
configuration = config
End Sub
Public Sub Start()
While True
Try
For Each job As ISchedulerJob In configuration.Jobs
job.Execute()
Next
Catch
Finally
Thread.Sleep(configuration.SleepInterval)
End Try
End While
End Sub
End Class
If you wish to execute the jobs only once at a specific time, then simply do not use the while/wend
loop and make sure you place a check if the current time (date.Now
) is greater than the scheduled time. In this case, you may also wish to set the schedule interval to seconds or minutes. That is all it takes to make the scheduler. To add jobs and execute them, you need to do the following:
- Create a new instance of the scheduler configuration (in my case, 1 hour)
- Create the jobs by creating a new instance of each job in the scheduler configuration
- Create new instance of the scheduler using the above config
- Create a new thread that will start with the sub named
Start
in the scheduler class
- Start the newly created thread
Private schedulerThread As System.Threading.Thread = Nothing
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
Dim config As New MailScheduler.SchedulerConfiguration(60 * 60 * 1000)
config.Jobs.Add(New MailScheduler.MailJob())
config.Jobs.Add(New MailScheduler.OtherJob())
Dim scheduler As New MailScheduler.Scheduler(config)
Dim mailThreadStart As _
New System.Threading.ThreadStart(AddressOf scheduler.Start)
schedulerThread = New System.Threading.Thread(mailThreadStart)
schedulerThread.Start()
End Sub
Sub Application_End(ByVal sender As Object, ByVal e As EventArgs)
If Not IsNothing(schedulerThread) Then
schedulerThread.Abort()
End If
End Sub
I've placed the above code in the global.asax on the application start with the schedulerThread
declared as a private variable within the scope of global.asax. The is because I will need to reference it on application end to kill the same thread. That would be a perfect place for any scheduled task to run and then just continue to the site. Now, as the site is working, our jobs will execute in a separate thread that sleeps between the job executions for a specified period of time.
If you wish the code to run as long as the ASP.NET worker process is alive, leave the scheduler thread (do not abort it). It is dangerous to leave any thread open, especially on a hosting provider, as it may cause undesired results. So be warned. The safer method is to create a public reference to the scheduler thread so you can reference it from within a page and abort it by simply loading the page (on the page load event). The approach on how to start and kill the thread is up to you. This article's intention was to give a simple code for implementing the scheduler functionality for recurring tasks. You may wish to search this site for more samples on creating safe threading models for your website.
Happy coding...