Asynchronous programming : async/await functions

By Samir Tine, published on June 2023

As seen in the first part of the "asynchrounous programming with LuaRT" tutorial, the Task object wraps all the complexity behind of managing and scheduling Lua coroutines.

In this part of the tutorial we will learn how to use two helpful global functions, famous in the Javascript/Python world, and available in Luart : the async() and await() functions.

The async() function

Let's bring back the asynchronous "Hello World" example from the first part of the tutorial :

function hello(name) sleep(2000) print("Hello "..name.." !") end task = sys.Task(hello) task("LuaRT") -- Wait fot the task to terminate task:wait()

async() is a utility function that will create and spawn a Task immediately, returning the result of the Task, or returning the Task itself if it has been put to sleep. The first parameter is the function to be executed asynchronously. Any other parameters are provided to the given function when starting the Task.


Here is the same example using the async() function :

function hello(name) sleep(2000) print("Hello "..name.." !") end task = async(hello, "LuaRT") task:wait()

Much simpler, isn't it? You can even sacrifice readbility for a shorter version :

async(function (name) sleep(2000) print("Hello "..name.." !") end, "luaRT"):wait()

But you should be careful with this kind of code as the Task may return a value without sleeping before. In this case, the async() function returns the task result directly:

function add_one(i) return i + 1 end print(async(add_one, 1))

In this example, the add_one() function returns immediately the result of 1+1, which is quite useless (this example is just for educational purpose).

Asynchronous "Hello World !"

Let's write a new version or our asynchronous "Hello World!" example, this time using the async() function :

function hello(delay, msg) sleep(delay) print(msg) end -- spawn the first asynchrounous Task (prints "Hello" after 1sec) async(hello, 1000, "Hello") -- spawn the second asynchrounous Task (prints "World" after 2sec) async(hello, 2000, "World") -- Wait for all tasks to terminate waitall()

As you can see, the async() function is a clean and simple way to use asynchronous programming with Luart.

Another global function is very useful too when running tasks concurrently,the await() function.

The await() function

The await() function blocks program execution flow until the provided task is terminated, while still being able to run other tasks concurrently. Here is the same previous "Hello World" example using await() :

function hello(delay, msg) sleep(delay) print(msg) end -- spawn the first asynchrounous Task (prints "Hello" after 1sec) async(hello, 1000, "Hello") -- spawn the second asynchrounous Task (prints "World" after 2sec) task = async(hello, 2000, "World") -- and wait until the last task is terminated await(task)

In this example, there is no need to wait for all tasks to terminate using waitall() as the last statement blocks program execution until the second tasks terminates, while still running the first task concurrently.
You can even spawn and wait for a single task using the await() function :

function hello(delay, msg) sleep(delay) print(msg) end -- spawn the first asynchrounous Task (prints "Hello" after 1sec) async(hello, 1000, "Hello") -- spawn the second asynchrounous Task (prints "World" after 2sec), waiting until its terminated await(hello, 2000, "World")

await() is a very handy function to wait for (and eventually spawn) a specific task.

Task objects have another interesting feature, that can be used in conjunction with the await() function : the Task.after property.

The Task.after property

The Task.after property is useful to execute code once a Task have been terminated its execution. This property expects a function, and can be used like this :

function hello(delay, msg) sleep(delay) print(msg) end -- spawn the first asynchrounous Task (prints "Hello" after 1sec) -- then, once its terminated, make another async call, and set the Task.after property to print the "done" message async(hello, 1000, "Hello").after = function() async(hello, 1000, "World").after = function() print("done") end end waitall()

As you can see, it is possible to use nested calls thanks to the `Task.after` property to ensure the order of tasks execution.
You can even await inside a `Task.after` call :

function hello(delay, msg) sleep(delay) print(msg) end -- spawn the first asynchrounous Task (prints "Hello" after 1sec) -- then, once its terminated, make another async call, and set the Task.after property to print the "done" message async(hello, 1000, "Hello").after = function() await(hello, 1000, "World") print("done") end waitall()

Well this first tutorial on concurrent programming with Luart comes to its end. You have learned the basics to create and start a new Task, to bring execution flow to other Tasks, and to wait on them.


The next part of the concurrent programming tutorial will cover the famous async/await functions.