Products

Solutions

Resources

Partners

Community

About

New Community Website

Ordinarily, you'd be at the right spot, but we've recently launched a brand new community website... For the community, by the community.

Yay... Take Me to the Community!

The Community Blog is a personal opinion of community members and by no means the official standpoint of DNN Corp or DNN Platform. This is a place to express personal thoughts about DNNPlatform, the community and its ecosystem. Do you have useful information that you would like to share with the DNN Community in a featured article or blog? If so, please contact .

The use of the Community Blog is covered by our Community Blog Guidelines - please read before commenting or posting.


Running on a Fixed Schedule

Overview

Schedule ClockA question recently came up in one of the forums concerning the DotNetNuke scheduler.  Specifically, the user wanted to know if it was possible to get the scheduler to run a process at a fixed time (this time might be static or it might be set through some administrative UI).  To answer this question, we first need to look at how the scheduler works.

Professional DotNetNuke 4 Chapter 8 provides some background information about programming the scheduler which will give us some context for our discussion.

"Because the Scheduler is run under the context of the web application, it is prone to the same types of application recycles as a web application. In a web-hosting environment, it is a common practice to conserve resources by recycling the worker process for a site periodically. When this happens, the Scheduler stops running. Therefore, the tasks run by the Scheduler do not run 24 hours a day, 7 days a week. They are executed according to a defined schedule, but they can only be triggered when the worker process is alive. For this reason, you cannot specify that a task should run every night at midnight. It is not possible in the web environment to meet this type of use case. Instead, you can specify how often a task is run by defining the execution frequency for each task. The execution frequency is defined as every x minutes/hours/days."

A key item to note from this paragraph: The scheduler runs completely at the whim of IIS and ASP.Net.  If IIS needs to shut down the worker process, for whatever reason, then the scheduler will strop running until the worker process restarts.  What this means is that the work process may not be running at a specific time when your scheduled event is supposed to execute.  The worker process will only restart if a request is made to the DotNetNuke application/website.

Solution

So given the limitations of the scheduler, how do we run a task at a fixed time?  The trick to making sure a task runs on time is to keep the worker process alive.  By default, ASP.Net will shutdown a worker process that has been inactive for 20 minutes.  To keep this from happening we need to have requests coming into the website more frequently than every 20 minutes.  On a busy site, this is not generally a problem.  Less active sites will often resort to using some sort of keep-alive service that makes requests to the website every 10-15 minutes.  This ensures that ASP.Net will not unload the worker process.  ASP.Net is also configured by default to recycle the application pool about once every 23 hours.  As long as you have a keep-alive service running, this should not pose any additional challenge.

So now that we have the scheduler running, we need to figure out how to have the scheduler run our task at a specific time.  Because of the previous limitations, the DotNetNuke scheduler does not come with a UI for specifying a fixed execution time.  By digging into the guts of the scheduler we can easily overcome this limitation.

When we create our scheduler task, we inherit from DotNetNuke.Services.Scheduling.SchedulerClient.  The standard constructor, as shown in Chapter 8, takes a ScheduleHistoryItem as a parameter, which is then saved to the SchedulerClient.

Public Sub New(ByVal objScheduleHistoryItem As DotNetNuke.Services.Scheduling.ScheduleHistoryItem)
     MyBase.new()
     Me.ScheduleHistoryItem = objScheduleHistoryItem    'REQUIRED
End Sub

Looking at the class diagram, we see that ScheduleHistoryItem inherits from ScheduleItem.  ScheduleItem includes the TimeLapse property that tells the scheduler the number of time units to wait until the next scheduled execution.  We can use this property to control when our task executes next.

ScheduleItem Class Diagram

When we are through performing our work in the DoWork method of our SchedulerClient, we will normally set the objScheduleHistoryItem.Succeeded property to true.  This signals the scheduler framework that our work is complete and that it succeeded.  The scheduler will use this information to set the next time to execute our task.  This is accomplished in the WorkCompleted method of the CoreScheduler class.  Buried in this method is the key to our workaround (the relevant portions of the code is shown below).

Shared Sub WorkCompleted(ByRef objSchedulerClient As SchedulerClient)
...

    Dim objScheduleHistoryItem As ScheduleHistoryItem
    objScheduleHistoryItem = objSchedulerClient.ScheduleHistoryItem

...

    Select Case objScheduleHistoryItem.TimeLapseMeasurement
        Case "s"
            objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddSeconds(objScheduleHistoryItem.TimeLapse)
        Case "m"
            objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddMinutes(objScheduleHistoryItem.TimeLapse)
        Case "h"
            objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddHours(objScheduleHistoryItem.TimeLapse)
        Case "d"
            objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddDays(objScheduleHistoryItem.TimeLapse)
    End Select

...

    'Update the ScheduleHistory in the database
    CoreScheduler.UpdateScheduleHistory(objScheduleHistoryItem)
    Dim objEventLogInfo As New LogInfo

...

End Sub

Notice that the ScheduleHistoryItem.TimeLapse of our task is saved back to the database.  This basically means that by calculating the timelapse in our SchedulerClient, and setting this value before we return from completing our work that we can control when the scheduler will next execute our task.  So after setting Succeeded, I just add another line of code to set the timelapse value.

The only step left is to include a method for determining the proper timelapse value.  This is a method I used on a previous project to run a task every morning at 6am.  It should not be too much of a stretch for you to come up with a method to read a ScheduleItemSettings property to calculate the correct timelapse. (NOTE:  My example assumes that the TimeLapseMeasurement is set to minutes, hence the use of TicksPerMinute)

Private Function GetElapsedTimeTillNextStart() As Integer
    Dim Tomorrow As DateTime = DateTime.Today.AddDays(1)

    ' This sets the start time for 6 AM
    Dim nextStart As New DateTime(Tomorrow.Year, Tomorrow.Month, Tomorrow.Day, 6, 0, 0)
    Dim elapseMinutes As Integer = (nextStart.Ticks - DateTime.Now.Ticks) \ TimeSpan.TicksPerMinute
    Return elapseMinutes
End Function

One final word of caution:  This method is not guaranteed to run your process at an exact time.  It may fluctuate by a couple minutes depending on your keep-alive duration, other currently executing tasks, and several other factors.  For my purposes, it was enough to know that my task would run every day at approximately 6am.  If you need your task to run at an exact time, then you should probably look at a solution that uses an external scheduler and avoids the ASP.Net pipeline.

Comments

There are currently no comments, be the first to post one.

Comment Form

Only registered users may post comments.

NewsArchives


Aderson Oliveira (22)
Alec Whittington (11)
Alessandra Daniels (3)
Alex Shirley (10)
Andrew Hoefling (3)
Andrew Nurse (30)
Andy Tryba (1)
Anthony Glenwright (5)
Antonio Chagoury (28)
Ash Prasad (37)
Ben Schmidt (1)
Benjamin Hermann (25)
Benoit Sarton (9)
Beth Firebaugh (12)
Bill Walker (36)
Bob Kruger (5)
Bogdan Litescu (1)
Brian Dukes (2)
Brice Snow (1)
Bruce Chapman (20)
Bryan Andrews (1)
cathal connolly (55)
Charles Nurse (163)
Chris Hammond (213)
Chris Paterra (55)
Clint Patterson (108)
Cuong Dang (21)
Daniel Bartholomew (2)
Daniel Mettler (181)
Daniel Valadas (48)
Dave Buckner (2)
David Poindexter (12)
David Rodriguez (3)
Dennis Shiao (1)
Doug Howell (11)
Erik van Ballegoij (30)
Ernst Peter Tamminga (80)
Francisco Perez Andres (17)
Geoff Barlow (12)
George Alatrash (12)
Gifford Watkins (3)
Gilles Le Pigocher (3)
Ian Robinson (7)
Israel Martinez (17)
Jan Blomquist (2)
Jan Jonas (3)
Jaspreet Bhatia (1)
Jenni Merrifield (6)
Joe Brinkman (274)
John Mitchell (1)
Jon Henning (14)
Jonathan Sheely (4)
Jordan Coopersmith (1)
Joseph Craig (2)
Kan Ma (1)
Keivan Beigi (3)
Kelly Ford (4)
Ken Grierson (10)
Kevin Schreiner (6)
Leigh Pointer (31)
Lorraine Young (60)
Malik Khan (1)
Matt Rutledge (2)
Matthias Schlomann (16)
Mauricio Márquez (5)
Michael Doxsey (7)
Michael Tobisch (3)
Michael Washington (202)
Miguel Gatmaytan (3)
Mike Horton (19)
Mitchel Sellers (40)
Nathan Rover (3)
Navin V Nagiah (14)
Néstor Sánchez (31)
Nik Kalyani (14)
Oliver Hine (1)
Patricio F. Salinas (1)
Patrick Ryan (1)
Peter Donker (54)
Philip Beadle (135)
Philipp Becker (4)
Richard Dumas (22)
Robert J Collins (5)
Roger Selwyn (8)
Ruben Lopez (1)
Ryan Martinez (1)
Sacha Trauwaen (1)
Salar Golestanian (4)
Sanjay Mehrotra (9)
Scott McCulloch (1)
Scott Schlesier (11)
Scott Wilkinson (3)
Scott Willhite (97)
Sebastian Leupold (80)
Shaun Walker (237)
Shawn Mehaffie (17)
Stefan Cullmann (12)
Stefan Kamphuis (12)
Steve Fabian (31)
Steven Fisher (1)
Tony Henrich (3)
Torsten Weggen (3)
Tycho de Waard (4)
Vicenç Masanas (27)
Vincent Nguyen (3)
Vitaly Kozadayev (6)
Will Morgenweck (40)
Will Strohl (180)
William Severance (5)
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out