Making Serial Code Concurrent with Tasks

Let’s learn how to handle long-running jobs in Elixir using async/await.

The natural go-to concurrency construct for Elixir beginners is the process. Still, we generally want to incorporate proven features that have already addressed the subtle complexities of concurrency. We’ll cover processes here so we can put them in context when the inevitable edge cases do occur.

We’ve already spent some time using Elixir primitives for processes, but it bears repeating. We have access to the primitives to do the following:

  • Send messages with send/2.

  • Spawn processes with the various versions of spawn/n and spawn_monitor/n.

Typically, we’ll want our processes to be OTP processes because we’ll want to take advantage of the GenServer lifecycle. Elixir can’t manage what it doesn’t know about.

We’ll rarely send messages with the send variants, though we might use some of its close cousins. The following are useful for dealing with various scheduling problems:

  • Process.send_after

  • :timer.send_interval

However, these are exceptions. The rule of thumb is to use higher abstractions to work with processes, so let’s move up the food chain to a pretty simple alternative to naked processes—the task.

Concurrency

Elixir has a nice abstraction for executing one-time single-purpose jobs in a process called the task. Generally, we’ll fire a task, do some work, and then await the results. Tasks are great for making sequential code concurrent. Let’s look at a couple of different ways to handle long-running jobs.

Async and await

An easy way to run two slow jobs concurrently is to use Task.async/1 and Task.await/1. We’ll create a standalone file tasks/task.ex and fill it with this:

Get hands-on with 1200+ tech skills courses.