DEV Community

Felipe Araujo
Felipe Araujo

Posted on • Edited on

O Poder do Módulo Task do Elixir - O Início

Caso prefira em inglês você pode encontrar aqui.

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

É 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)
Enter fullscreen mode Exit fullscreen mode

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.
Task.await(task)
Task.await(task, :infinity)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Como podemos ver, um tempo limite é um pouco explosivo ao usar Task.await. Uma maneira de lidar melhor com isso é são 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}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)