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)
{: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.
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
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 e conheça mais sobre a SumUp.
Por: Felipe Araujo, Software Engineer
Top comments (0)