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.