Share via


Azure Functions: Asynchronous Programming

Introduction

 Azure Functions is handy when it comes to Serverless and FaaS computations. It does provide a very flexible architecture to define functions and consume them. Sometimes we may have to program in an asynchronous manner (eg: if we want to talk with DB from the function / CRUD operation) and it is listed as one of the best practice for the development of Azure Functions.  In this post, we will discuss how to create Azure Functions that do operations in an async manner.

Using Task.Run()

Task.Run is one of the classic ways to invoke asynchronous calls. Since this accepts an Action, it queues it to run on the thread pool and returns a Task object. But Azure guidelines tell us not to refer the Result property of the task nor call Wait() on the Task instance as this may result in thread pool exhaustion. So, since we should avoid them, the function can be coded as below.

public static void Run(string myFile, TraceWriter log)
{
    try
    {
        Task.Run(() => UpdateToDBTable(myFile, DateTime.Now));
        log.Info($"C# File trigger function saved file: {myFile} info to DB successfully");
    }
    catch(Exception ex)
    {
        log.Info($"Exception found {ex.Message}");
    }
}

This works fine, and the UpdateToDBTable action will run in the background and the Functions runtime determines the action is successful.

There is a problem with this approach when we use a Service Bus Queue trigger for functions. That is, say we're going to consume messages in a particular Service Bus Queue and mark them as "completed" when its consumed successfully. Consider the below code

public static void Run(string mycloudQueueTrigger, TraceWriter log)
{
    try
    {
        Task.Run(() => UpdateToDBTable(myFile, DateTime.Now));
        log.Info($"C# File trigger function saved file: {myFile} info to DB successfully");
    }
    catch(Exception ex)
    {
        log.Info($"Exception found {ex.Message}");
    }
}

It is the same code, assuming the bindings were used, we're just changing the first parameter of the Run() method.

If an error occurs when we tried to Update to the Database Table, the message will still be marked as completed and it should not happen as we'll miss the message in this approach. So to avoid it, and not mark the message as "completed", we can use the following approach.

Using async await

To use await we can simply modify the definition of the function as public static async Task Run() and later we can call the Database talking stub with await. So if we take the same example mentioned above, it can be re-written as below

public static async Task Run(string mycloudQueueTrigger, TraceWriter log)
{
    try
    {
        await UpdateToDBTable(myFile, DateTime.Now);
        log.Info($"C# File trigger function saved file: {myFile} info to DB successfully");
    }
    catch(Exception ex)
    {
        log.Info($"Exception found {ex.Message}");
    }
}

In this approach, we're treating the entire function as a Task and a failure in the awaited call will result in the failure of the entire function.

So, the second approach will be the most suitable and meaningful approach when it comes to Asynchronous Programming in Azure Functions and it suits all scenarios. 

Happy Coding.