Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Task scheduler for websites

0.00/5 (No votes)
21 Mar 2008 1  
Create scheduled tasks for your website (no third party or heavy coding involved).

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:

  1. The ISchedulerJob interface
  2. Classes that implement the interface above
  3. Scheduler configuration class
  4. 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:

  1. Create a new instance of the scheduler configuration (in my case, 1 hour)
  2. Create the jobs by creating a new instance of each job in the scheduler configuration
  3. Create new instance of the scheduler using the above config
  4. Create a new thread that will start with the sub named Start in the scheduler class
  5. 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...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here