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 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)
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 emTask.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.
Top comments (0)