O Poder do Módulo Task do Elixir - O Início
Published: 2021-05-14 20:46:20

Caso prefira em inglês você pode encontrar [aqui](https://dev.to/felipearaujos/the-power-of-elixir-task-module-54np).

Recentemente eu estava estudando mais profundamente como o módulo *Task* funciona e para consolidar meus estudos decidi escrever esse post.

Antes de começar, vamos ver a documentação do módulo [task](https://hexdocs.pm/elixir/Task.html). O melhor lugar para fazer isso é a documentação oficial do elixir. Aqui nós temos:

> 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**

Ótima definição mas `me mostre o código!!!!`

## Iniciando uma operação async
Nós temos duas maneiras básicas para executar uma operação *async* usando `Task`. É possível usar `Task.start` e `Task.async`. Vamos ver como isso funciona na prática.

**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>
}
```

É possível ver que `Task.start` retorna uma tupla com `:ok` e `PID`, enquanto `Task.async` retorna uma `struct` *Task*. Ambas funções funcionam da mesma forma.

Geralmente precisamos esperar o resultado de algumas funções *async* para assim, poder executar a próxima ação. Vamos lá!

## Esperando resultados
O exemplo anterior foi meio básico, vamos primeiro adicionar alguma um *delay* na execução para torna-lo mais complexo.

```elixir
  Task.async(fn ->
    :timer.sleep(5000)
    IO.inspect("Hello")
    :ok
  end)
```

Como já visto o resultado será uma `%Task{}`. Para esperar a response temos duas opções `Task.await` e `Task.yield`. Vamos ver as diferenças:

**Task.await**
- O tempo de `timeout` padrão é 5 segundos;
- Dado um *timeout*, ele lança uma exceção;
- Após o tempo limite ser atingido, a `task` é interrompida;
- Você pode definir um tempo limite personalizado ou usar o atom`:infinity`.

```elixir
Task.await(task)
Task.await(task, :infinity)
```
Exemplo de timeout
```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
```
Como podemos ver, um tempo limite é um pouco explosivo ao usar `Task.await`. Uma maneira de lidar melhor com isso é são  [Supervised Tasks] (https://hexdocs.pm/elixir/Task.html#module-supervised-tasks).

**Task.yield**
- O tempo de `timeout` padrão é 5 segundos;
- Dado um timeout, ele retorna `nil`;
- Usar o atom `:infinity` não é permitido como em `Task.await`;
- Depois de um timeout atingido, mantém a task em execução;
- É possível terminar uma task em execução usando `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}
```
Dado um resultado `:timeout`, a resposta será `nil`. Depois disso, podemos executar `Task.yield` novamente. Para evitar task de longa execução sem nenhum resultado, você pode usar `Task.shutdown (task, shutdown \\ 5000)`.

# Um exemplo mais completo
Temos uma lista de itens e é necessário executar algum processamento em todos itens da lista.

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

Com essa abordagem, ainda temos a exceção quando acontece o *timeout*.  No entanto, é possível lidar com isso de uma maneira melhor com [Supervised Tasks] (https://hexdocs.pm/elixir/Task.html#module-supervised-tasks), mas esse assunto será abordado na [próxima publicação] (https://dev.to/felipearaujos/the-power-of-elixir-task-module-into-task-supervisor-5365).

### Conteúdo adicional
- [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/)