O Poder do Módulo Task do Elixir - O Início
Tags: elixir
Published: 2021-11-30 13:25:32

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**
Task.start(fn -> IO.inspect("Hello") end)
{:ok, #PID<0.114.0>}

**Task.async**
> Task.async(fn -> IO.inspect("Hello") end)
> %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.

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

Como vimos 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.

> Task.await(task)
> Task.await(task, :infinity)

**Exemplo de timeout**
> 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).

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

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, mas esse assunto será abordado na próxima publicação.

##**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/)

------------------

*Originalmente publicado em https://dev.to on May 14, 2021.*

------------------
Quer uma nova oportunidade de carreira com desafios globais? Confira nossas vagas em [Engenharia e Produto] (https://go.sumup.com.br/eb-dev-to-vagas) e conheça mais sobre a SumUp.

Por: Felipe Araujo, Software Engineer