Wednesday, August 9, 2017

Run asynchronous code on schedule with Azure WebJobs

This short post describes how you can have a scheduled Azure WebJob that runs an asynchrouns method.

The problem with this scenario is that the WebJob, which is just a regular console application, cannot have an asynchronous Main method. So you need a way around that. Next to that you would to be able to trigger that method with a schedule.

In my solution I use a Settings.job text file that is in the root folder of the WebJob (Set copy to out put directory to 'Copy Always' in the properties of the file in Visual Studio.
The contents of the file contain JSON with a cron expression like so:
{
    "schedule": "0 0 6/12 * * *"
}
This is set to every twelve hours starting from 6:00. So far so good, and nothing you probably havent seen before or in other blogs.

Now suppose you used the Visual Studio template to generate the WebJob Project. This creates the main Program.cs file and also a Functions.cs file. The Functions file is meant for you to register yor methods that serve as triggers. I tried using the TimerTriggered function like below, but the result wasn't that good. If you Run and block the JobHost in the Program.cs (as is done in the VS template), this means that your job will always show a running state and the timertrigger will go off, but only if it's within the time out period of the webjob which is 120 seconds. So that is a crap solution.

Instead, define a manual trigger with the tag [NoAutomaticTrigger]. We will call this method from the Main method. Notice that this method is asynchronous. I use code in there to fetch from a API and save back to a different service that I already wrote and that is asynchronous.

public class Functions
    {
        public static async Task TimerTriggered([TimerTrigger("0 0 6/12 * * *")] TimerInfo timer, TextWriter log)
        {
            await Functions.RunJob(log);
        }

        [NoAutomaticTrigger]
        public static async Task ManualTrigger(TextWriter log)
        {
            await Functions.RunJob(log);
        }

Now, code your Main method in Program.cs to call and await the method, using the JobHost.CallAsync method inside a Task.Run delegate.

class Program
    {
        static void Main()
        {


            var host = new JobHost();
            Task aTask = Task.Run(() => host.CallAsync(typeof(Functions).GetMethod("ManualTrigger")));

            aTask.Wait();          
        }
    }

The solution is to use the Task class to run and await the asynchronous method. With the JobHost.CallSync you can call the method in another class. Parameters like the TextWriter are passed by the JobHost.

1 comment:

  1. Hello! Thanks for this. Would there be any advantage to calling it with an async delegate so you can await it? I.e.

    Task.Run(async () => await host.CallAsync(typeof(Functions).GetMethod("ManualTrigger"))).Wait();

    I'm guessing that your version might return the CallAsync Task immediately to the Wait, whereas mine might 'wait' for the 'await host.CallAsync', and then the enclosing Wait() returns immediately?

    ReplyDelete