17 Comments

It's hard to schedule tasks on Azure: The platform doesn't have built-in support for executing custom code based on a schedule. There's some options to get around this though, for example:

  • Azure VM and the Windows Task Scheduler
  • Azure Service Bus and ScheduledEnqueueTimeUTC

But these have some limitations which may be hard to get around: In the case of a VM, if it goes down, everything stops. With Azure Service Bus you need some service which handles the scheduling.

This is where the Quartz.net fits in. Quartz.net is a flexible and easy-to-use library for running scheduled tasks. And it works great on Azure.

Here's some highlights of Quartz.net's features:

  • Supports clusters
  • Can use SQL Azure
  • Cron-style syntax for scheduling tasks

In this tutorial we build a Quartz.net (version 2.0.1) cluster using SQL Azure and ASP.NET.

The database

First we need to set-up the SQL Azure database. Quartz.net will persist all the job details and triggers to this database. Easiest way to create the required tables is to use Management Studio to log in to the database and to execute the Quartz.net database schema creation script.

And that's it for the database. Next step is to build the service for running the Quartz.net.

The ASP.NET project

The SQL Azure store is ready and now we need a service which will run actions based on the triggers. The service can be almost anything: a console app, a Windows Service or an ASP.NET project. In this tutorial we're going to use an ASP.NET project which is deployed to two extra small Cloud Service instances.

Here are the steps required for creating the service:

  1. Create new project with template "ASP.NET Empty Web Application"
  2. Use NuGet to install package "Quartz"
  3. Add New Item to project using template "Global Application Class" (global.asax)

Modify the Global.asax.cs so that the Quartz.net starts and stop with the application:

using System;
using Common.Logging;
using Quartz;
using Quartz.Impl;

namespace QuarzApp
{
    public class Global : System.Web.HttpApplication
    {
        public static IScheduler Scheduler;

        protected void Application_Start(object sender, EventArgs e)
        {
            ISchedulerFactory sf = new StdSchedulerFactory();
            Scheduler = sf.GetScheduler();

            Scheduler.Start();
        }

        protected void Application_End(object sender, EventArgs e)
        {
            Scheduler.Shutdown();
        }
    }

}

And finally modify the Web.config to include the Quartz.net configuration:

<?xml version="1.0"?>
<configuration>

  <configSections>
    <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <sectionGroup name="common">
      <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/>
    </sectionGroup>
  </configSections>

  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>

  <common>
    <logging>
      <factoryAdapter type="Common.Logging.Simple.TraceLoggerFactoryAdapter, Common.Logging">
        <arg key="showLogName" value="true"/>
        <arg key="showDataTime" value="true"/>
        <arg key="level" value="INFO"/>
        <arg key="dateTimeFormat" value="HH:mm:ss:fff"/>
      </factoryAdapter>
    </logging>
  </common>

  <quartz>
    <add key="quartz.scheduler.instanceName" value="MyScheduler" />
    <add key="quartz.scheduler.instanceId" value="AUTO" />

    <add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
    <add key="quartz.threadPool.threadCount" value="5" />
    <add key="quartz.threadPool.threadPriority" value="Normal" />

    <add key="quartz.jobStore.useProperties" value="true" />
    <add key="quartz.jobStore.clustered" value="true" />
    <add key="quartz.jobStore.misfireThreshold" value="60000" />
    <add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
    <add key="quartz.jobStore.tablePrefix" value="QRTZ_" />

    <add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" />

    <add key="quartz.jobStore.dataSource" value="myDS" />

    <add key="quartz.dataSource.myDS.connectionString" value="Server=tcp:myserver.database.windows.net,1433;Database=mydatabase;User ID=myuser@myserver;Password=mypassword;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" />
    <add key="quartz.dataSource.myDS.provider" value="SqlServer-20" />
  </quartz>

</configuration>

That's it. The service for running the scheduled jobs is ready. As the last step we're going to create a simple job and a schedule for it.

Example job

All the bits and pieces are ready and only thing left is to test out the service. For that we need a job to execute and a trigger which will handle the execution.

The Quartz.net jobs implement a simple IJob interface. The following job will log a message when it is executed:

    public class MyJob : IJob
    {
        private static ILog logging = LogManager.GetLogger(typeof (MyJob));

        public void Execute(IJobExecutionContext context)
        {
            var mydata = context.MergedJobDataMap["data"];

            logging.InfoFormat("Hello from job {0}", mydata);
        }
    }

To execute a job we need a trigger for it. Here's an example code which will add a single trigger for the job, making it to execute after 15 seconds has passed. Create a Web Form "Default.aspx" to the project, add a button to it and in its click handler create a new trigger and add it to the scheduler:

        protected void Button1_Click(object sender, EventArgs e)
        {
            var startTime = DateTimeOffset.Now.AddSeconds(15);

            var job = JobBuilder.Create<MyJob>()
                                .WithIdentity("job1", "group1")
                                .Build();

            var trigger = TriggerBuilder.Create()
                .WithIdentity("trigger1", "group1")
                .StartAt(startTime)
                .Build();

            Global.Scheduler.ScheduleJob(job, trigger);
        }

Deployment and application pools

As the service uses ASP.NET, it can be deployed to Azure Web Sites or to Azure Cloud Services. One thing to keep in mind is that the IIS will recycle the application pools automatically. This will shut down the application and the Quartz.net with it. To get around this limitation, it may be wise to change the application pool's recycling interval to 0.

Another option is to create Quarz.net job which "pings" the current web site once in every 10 minutes or to use some other keep-alive service for the pinging.

Links

Comments

Comment by Create Windows Service Net

[...] Configuring Cluster using SQL Azure and ... Quartz.net is a flexible and easy-to-use library for running scheduled tasks. In this tutorial we build a cluster using SQL Azure and The project. The SQL Azure store is ready and now we need a service which will run actions based on the triggers. The service can be almost anything: a console app, a Windows Service or an project. In this tutorial we're going to use an project which is deployed to two extra small . [...]

Comment by r4 kaart

r4 kaart...

d''''Auteuil une nouvelle qui promet d''''être extrêmement chaude. Dans the Prix Robert environnant les Crmont Tonnerre, Première des quatre préparatoires officielles large...

Comment by Cheap Michael Kors bags

Cheap Michael Kors bags...

Monster Beats by Dr. Dre headphones Christmas Special...

Comment by Louis Vuitton bags

Louis Vuitton bags...

time of year, specific sales will popular Internet destination, attracting more people to upgrade their headphones, their arsenal headphones ....

Comment by wholesale designer handbags

wholesale designer handbags...

Configuring Quartz.net Cluster using SQL Azure and ASP.NET - Mikael Koskinen cheap designer bags...

Comment by Louis Vuitton Outlet

Louis Vuitton Outlet...

I simply couldn’t leave your web site prior to suggesting that I actually loved the usual info an individual provide for your guests? Is gonna be again often to check up on new posts...

Comment by Soccer Jerseys Wholesale

Soccer Jerseys Wholesale...

Hi If you want to buy soccer jerseys or football shirts you can to soccerjerseybox.com as a football fans...

Comment by ray ban outlet sunglasses

ray ban outlet sunglasses...

I find it really hard to believe that anyone could agree on everything any church teaches. So finding out there are so many who do disagree is refreshing to me. People have brains and they should use them. No church IMO should have total control of any...

Comment by Tamoj

I am getting error from SQL Azure with Quartz tables created using the script link in the blog.

Error clearing scheduling data: Tables without a clustered index are not supported in this version of SQL Server. Please create a clustered index and try again.

Tamoj
Comment by Mikael Koskinen

Hmh, strange. I just run the script to an empty database and it completed successfully. There's different scripts for SQL Server and SQL Server CE. I remember that the plain SQL Server script threw some errors but the CE-script worked without modifications.

Comment by Ricardo Barona

Hi Mikael Koskinen

I noticed you are using [quartz] schema for database tables i.e. [quartz].[QRTZ_LOCKS].
I'm using a custom schema too, but when I'm trying to schedule a new Job
i.e. myScheduler.ScheduleJob(myJob, myTrigger);
I'm getting an "Invalid object name 'QRTZ_LOCKS'." which is the same error I get if I run below SQL statement from SQL Server Managemente Studio:

SELECT * FROM
[QRTZ_LOCKS]

Now, if I do

SELECT * FROM
[myschema].[QRTZ_LOCKS]



it works! (in SQL Management Studio)


So my question is, do I have to specify the schema in some place?
In your example, do you have more than one schema in your database?

Appreciate your help.

Ricardo Barona
Comment by Ricardo Barona

Never mind, I just tried with


and it worked.


Thanks anyway, and thanks for your post it was really clear and very helpful.

Ricardo Barona
Comment by Mikael Koskinen

Hi Ricardo,


Glad to hear that you got it working and thanks for sharing the solution!

Comment by Marko Lahma

The latest 2.2.2 release has fixed the long-standing issue with the create script. Now the tables_sqlServer.sql will run correctly against SQL Azure. Maybe this blog post should be updated to refer to normal SQL Server script instead of the CE version.

Marko Lahma