Using Tasks in Elixir

A couple of weeks ago we looked at working with processes in Elixir. To create a new process we can use the spawn/1 function. This creates a new process and executes the given function concurrently to the current process.

Elixir also provides the Task module that builds upon these basic spawn functions to provide convenience methods and better error reports.

Tasks can also be supervised as part of supervision trees. We haven’t looked into supervision trees yet, so we won’t touch upon that aspect of Tasks in this tutorial. However, we will be taking a deeper look into supervision trees in a future tutorial.

Instead, today lets take a look at using Tasks in Elixir.

Starting a Task

The easiest way to get started with Tasks is to use the start/1 function:

This will create a new process and return the pid. However, when using the Task module, the pid will be wrapped in a tuple, where the first element is the atom :ok.

As with the spawn/1 function, this is fine if you don’t really care about the result. For example, if you just wanted to fire and forget you could use this function.

The Task module also has the start_link/1 function which, like the spawn_link/1 function, will create a processes where if there is a crash, that crash will be propagated to the original process.

We’re not going to be looking into supervising process in this tutorial so I will leave it at that.

In a future tutorial we are going to be going into much more depth into this topic as it’s a really interesting aspect of Elixir and Erlang.

Async and Await

Whilst sometimes it is fine to just fire and forget, you will often want to get a result back from running the task. For example, if you want to move a piece of sequential code to become concurrent by computing a value asynchronously.

To do that we can use the async/1 and await/1 functions

First we can use the async/1 function to create a new process and execute a function, just like we saw with the start/1 function in the last section:

task = Task.async(fn -> 2 * 2 end)

However, unlike the start/1 function from the last section, the return value of the async function is a %Task struct (What are Elixir Structs?):

%Task{pid: #PID<0.63.0>, ref: #Reference<0.0.3.77>}

This struct can then be passed to the await/1 function to return the computed value from the async/1 function:

Task.await(task)

You can now move time consuming work into a Task and then grab the result when you need it: