DEV Community

Felipe Araujo
Felipe Araujo

Posted on • Edited on

22 3

The Power of Elixir Task Module - The beginning

Recently i was studying more deeply how the elixir Task module works and to consolidate my study i decided to write this post.

Before we start, let's check the definition of the Task module. The best place to do it is in the Elixir official documentation. There we have:

Conveniences for spawning and awaiting tasks.

Tasks are processes meant to execute one particular action throughout their lifetime, often with little or no communication with other processes. The most common use case for tasks is to convert sequential code into concurrent code by computing a value asynchronously

Good definition, but show me the code!!

Start an async operation

We have two basic ways to execute an async operation using Task. It's possible to use Task.start and Task.async. Let's see how it works.

Task.start

Task.start(fn -> IO.inspect("Hello") end)
Enter fullscreen mode Exit fullscreen mode
{:ok, #PID<0.114.0>}
Enter fullscreen mode Exit fullscreen mode

Task.async

Task.async(fn -> IO.inspect("Hello") end)
Enter fullscreen mode Exit fullscreen mode
%Task{
  owner: #PID<0.110.0>,
  pid: #PID<0.118.0>,
  ref: #Reference<0.626386777.2138832899.106529>
}
Enter fullscreen mode Exit fullscreen mode

It's possible to see that Task.start returns a tuple with :ok and a process id, while Task.async returns a Task struct. However, both work in the same way.

Sometimes we need to wait some async process result to execute the next step. Let's do it!

Waiting for results

The previous sample was very basic. Adding some delay will make it better.

  Task.async(fn ->
    :timer.sleep(5000)
    IO.inspect("Hello")
    :ok
  end)
Enter fullscreen mode Exit fullscreen mode

As we know, the response will be a %Task{} struct. To await for a response we have two options: Task.await and Task. yield. Let's check their differences:

Task.await

  • Default timeout is 5 seconds;
  • Given a timeout, it throws an exception;
  • After the timeout is reached, the task is stopped;
  • You can define a custom timeout or use the atom :infinity.
Task.await(task)
Task.await(task, :infinity)
Enter fullscreen mode Exit fullscreen mode

Timeout sample

> task = Task.async(fn -> IO.inspect("Hello") ; :timer.sleep(10000); :ok end)

> Task.await(task)
"Hello"
** (exit) exited in: Task.await(%Task{owner: #PID<0.110.0>, pid: #PID<0.124.0>, ref: #Reference<0.3761442499.262406148.76432>}, 5000)
    ** (EXIT) time out
    (elixir 1.11.3) lib/task.ex:643: Task.await/2
Enter fullscreen mode Exit fullscreen mode

As we can see, a timeout is a little explosive when using Task.await. A way to handle it better is to use Supervised Tasks.

Task.yield

  • Default timeout is 5 seconds;
  • Given a timeout, it returns nil;
  • Using the atom :infinity is not allowed as on Task.await;
  • After the timeout is reached, it keeps the task running;
  • It's possible to finish a running task using Task.shutdown(task, shutdown \\ 5000).
> task = Task.async(fn -> IO.inspect("Hello") ; :timer.sleep(10000); :ok end)
> Task.yield(task)
nil
# Let's check again
> Task.yield(task)
{:ok, :ok}
Enter fullscreen mode Exit fullscreen mode

Given a :timeout result, the response will be nil. After that, we can execute Task.yield again. To avoid long running tasks without any results, you have to use Task.shutdown(task, shutdown \\ 5000).

A more complete sample

We have a list of items and it's necessary to execute some work in all of them:

    items = ["alpha", "beta", "gama"]

    Enum.map(items, fn item ->
      Task.async(fn ->
        :timer.sleep(4000)
        IO.inspect("Hello #{item}")
        :ok
      end)
    end)
    |> Enum.map(&Task.await/1)
    |> function_to_handle_results()
Enter fullscreen mode Exit fullscreen mode

With this approach, we still have the exception given a timeout is reached. However, it's possible to handle it in a better way with Supervised Tasks, but this subject will be covered in the next post.

Additional content

Billboard image

Synthetic monitoring. Built for developers.

Join Vercel, Render, and thousands of other teams that trust Checkly to streamline monitor creation and configuration with Monitoring as Code.

Start Monitoring

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay