The Power of Elixir Task Module - The beginning
Tags: elixir, async, task
Published: 2021-04-23 16:35:37

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](https://hexdocs.pm/elixir/Task.html) 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**
```elixir
Task.start(fn -> IO.inspect("Hello") end)
```
```elixir
{:ok, #PID<0.114.0>}
```

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

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.
```elixir
  Task.async(fn ->
    :timer.sleep(5000)
    IO.inspect("Hello")
    :ok
  end)
```
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`. 

```elixir
Task.await(task)
Task.await(task, :infinity)
```
Timeout sample
```elixir
> 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
```

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](https://hexdocs.pm/elixir/Task.html#module-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)`.

```elixir
> 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}
```
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:

```elixir
    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()
```

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](https://hexdocs.pm/elixir/Task.html#module-supervised-tasks), but this subject will be covered in the [next post](https://dev.to/felipearaujos/the-power-of-elixir-task-module-into-task-supervisor-5365).


### Additional content
- [Elixir Findings: Asynchronous Task Streams](https://dinojoaocosta.medium.com/elixir-findings-asynchronous-task-streams-7f6336227ea)
- [The problem with Task.await/2 and timeouts](https://angelika.me/2021/03/13/the-problem-with-task-await-and-timeouts/)