Conveniences for spawning and awaiting for tasks.
Tasks are processes meant to execute one particular action throughout their life-cycle, often with little or no communication with other processes. The most common use case for tasks is to compute a value asynchronously:
task = Task.async(fn -> do_some_work() end)
res = do_some_other_work()
res + Task.await(task)
Tasks spawned with async
can be awaited on by its caller
process (and only its caller) as shown in the example above.
They are implemented by spawning a process that sends a message
to the caller once the given computation is performed.
Besides async/1
and await/2
, tasks can also be
started as part of supervision trees and dynamically spawned
in remote nodes. We will explore all three scenarios next.
async and await
The most common way to spawn a task is with Task.async/1
. A new
process will be created, linked and monitored by the caller. Once
the task action finishes, a message will be sent to the caller
with the result.
Task.await/2
is used to read the message sent by the task. On
await
, Elixir will also setup a monitor to verify if the process
exited for any abnormal reason (or in case exits are being
trapped by the caller).
Supervised tasks
It is also possible to spawn a task inside a supervision tree
with start_link/1
and start_link/3
:
Task.start_link(fn -> IO.puts "ok" end)
Such tasks can be mounted in your supervision tree as:
import Supervisor.Spec
children = [
worker(Task, [fn -> IO.puts "ok" end])
]
Since these tasks are supervised and not directly linked to
the caller, they cannot be awaited on. Note start_link/1
,
unlike async/1
, returns {:ok, pid}
(which is
the result expected by supervision trees).
Supervision trees
The Task.Supervisor
module allows developers to start supervisors
that dynamically supervise tasks:
{:ok, pid} = Task.Supervisor.start_link()
Task.Supervisor.async(pid, MyMod, :my_fun, [arg1, arg2, arg3])
Task.Supervisor
also makes it possible to spawn tasks in remote nodes as
long as the supervisor is registered locally or globally:
# In the remote node
Task.Supervisor.start_link(name: :tasks_sup)
# In the client
Task.Supervisor.async({:tasks_sup, :remote@local}, MyMod, :my_fun, [arg1, arg2, arg3])
Task.Supervisor
is more often started in your supervision tree as:
import Supervisor.Spec
children = [
supervisor(Task.Supervisor, [[name: :tasks_sup]])
]
Note that, when working with distributed tasks, one should use the async/3
API,
that expects explicit module, function and arguments, instead of async/1
that
works with anonymous functions. That's because the anonymous function API expects
the same module version to exist on all involved nodes. Check the Agent
module
documentation for more information on distributed processes, as the limitations
described in the agents documentation apply to the whole ecosystem.
Finally, check Task.Supervisor
for other operations supported by the Task
supervisor.