How to run a scheduled task

  • .NET
  • Windows

A common need is to initiate regular treatment, for example every day at midnight and at noon.

To answer this problem, you need to ask yourself more questions than just how to perform the task at the scheduled time:

  • When should the task be executed in case the machines are spread over several time zones? Is this local noon or noon at Zulu (GMT) or Wisky (sober it corresponds to GMT-10)?
  • What happens if the execution of the task is abnormally long?
  • What happens if it fails? Should we restart it? And if it fails several times in a row?
  • Should the task run while the previous one is still running?
  • What if the machine was stopped when the job was to run?
  • How to log errors?
  • Which user account to use to start the task? (and think about the problem of the passwords)

And surely other issues that I do not think. In short, it's still a lot more than just starting a task at a regular time.

Since we do not like to reinvent the wheel, the first thing to say is that a system used by more than a billion users must provide a way to do something so common. And since Windows is rather well done (much more than we want to say), it provides an answer to this need: the Task Scheduler.

Windows Task Scheduler

The first problems are treated as we can see:

For logging, the Task Scheduler takes care of everything and saves as much information as possible: the beginning of the task, the actions performed and the result thereof, the end of the task, etc.

As for user accounts, there as always, it's more complicated.

The simplest is to start the task under the account of the current user only if he is connected. Microsoft thought about the UAC with the last option "Run with highest privileges" to use the unfiltered security token (ie with all groups and privileges) to run the task.

The "Do not store password" option uses Service-for-User (an extension of Kerberos RFC 1510) that does not allow the task to access a resource on the network. In addition the user must have the privilege "Logon as batch job"

In order for the task to access resources on the network without necessarily being connected, the password must be saved. The problem is the management of this password. We sometimes see passwords unchanged for 5 years, which of course is not a good practice.

Windows Server 2008 R2 introduced the Managed Service Account, accounts that can be used for services and for which Windows manages to manage the password. Windows Server 2012 extended this notion with the Group Managed Service Account, which is not limited to Windows services. The procedure to create and use this type of account for a scheduled task is shown here: http://blogs.technet.com/b/askpfeplat/archive/2012/12/17/windows-server-2012-group-managed-Service-accounts.aspx

Microsoft SQL Server

SQL Server also provides a task scheduler with similar functionality except that the task runs under the service account of the SQL Server Agent. Its purpose is mainly to interact with the SQL server but it is possible to launch a command or a PowerShell script.

Quartz.NET

Quartz.net is the port of Quartz, Java library, in .NET. This library provides many options to always achieve its ends.

Here is a small example:

public class Job : IJob
{
   public void Execute(IJobExecutionContext context)
   {
   Console.WriteLine(DateTime.Now);
   }
}
ISchedulerFactory schedFact = new StdSchedulerFactory();
IScheduler sched = schedFact.GetScheduler();
sched.Start();

// Create the task
IJobDetail job = JobBuilder.Create<Job>()
    .WithIdentity("Job", "Demo")
    .Build();
ITrigger trigger = TriggerBuilder.Create()
    .WithIdentity("Trigger", "Demo")
    .StartNow()
    .WithSimpleSchedule(x => x.WithIntervalInSeconds(5)
    .RepeatForever())
    .Build();
sched.ScheduleJob(job, trigger);

ASP.NET Cache

A solution that I find rather fun is based on the expiration of an element in the ASP.NET cache:

protected void Application_Start(object sender, EventArgs e)
{
    AddTask("DoStuff", 5);
}

private void AddTask(string name, int seconds)
{
    HttpRuntime.Cache.Insert(name, seconds, null, DateTime.Now.AddSeconds(seconds), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, CacheItemRemoved);
}

public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
{
    // TODO: Execute the task

    AddTask(k, Convert.ToInt32(v)); // reschedule the task
}

Obviously this solution is very limited in terms of functionality and is not very accurate. Moreover, launching a task in the same process as the website is a bad idea. Nothing tells us when recycling the app pool will take place (maybe in the middle of the task 😦)

Conclusion

There are several ways to start a task regularly. Personally I opt for the first 2 because they were designed to handle a maximum of cases and are easy to configure.

Follow me:
Enjoy this blog?Buy Me A CoffeeDonate with PayPal

Leave a reply