Elixir v1.0.5 Task

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.

Summary

Functions

The Task struct

Starts a task that can be awaited on

Starts a task that can be awaited on

Awaits a task reply

Receives a group of tasks and a message and finds a task that matches the given message

Starts a task

Starts a task

Starts a task as part of a supervision tree

Starts a task as part of a supervision tree

Types

t :: %Task{pid: term, ref: term}

Functions

__struct__()

Specs

__struct__ :: %Task{pid: term, ref: term}

The Task struct.

It contains two fields:

  • :pid - the process reference of the task process; it may be a pid or a tuple containing the process and node names

  • :ref - the task monitor reference
async(fun)

Specs

async((... -> any)) :: t

Starts a task that can be awaited on.

This function spawns a process that is linked to and monitored by the caller process. A Task struct is returned containing the relevant information.

Task’s message format

The reply sent by the task will be in the format {ref, msg}, where ref is the monitoring reference held by the task.

async(mod, fun, args)

Specs

async(module, atom, [term]) :: t

Starts a task that can be awaited on.

Similar to async/1, but the task is specified by the given module, function and arguments.

await(task, timeout \\ 5000)

Specs

await(t, timeout) :: term | no_return

Awaits a task reply.

A timeout, in milliseconds, can be given with default value of 5000. In case the task process dies, this function will exit with the same reason as the task.

find(tasks, msg)

Specs

find([t], any) :: {term, t} | nil | no_return

Receives a group of tasks and a message and finds a task that matches the given message.

This function returns a tuple with the task and the returned value in case the message matches a task that exited with success, it raises in case the found task failed or nil if no task was found.

This function is useful in situations where multiple tasks are spawned and their results are collected later on. For example, a GenServer can spawn tasks, store the tasks in a list and later use Task.find/2 to see if incoming messages are from any of the tasks.

start(fun)

Specs

start((... -> any)) :: {:ok, pid}

Starts a task.

This is only used when the task is used for side-effects (i.e. no interest in its return result) and it should not be linked to the current process.

start(mod, fun, args)

Specs

start(module, atom, [term]) :: {:ok, pid}

Starts a task.

This is only used when the task is used for side-effects (i.e. no interest in its return result) and it should not be linked to the current process.

start_link(fun)

Specs

start_link((... -> any)) :: {:ok, pid}

Starts a task as part of a supervision tree.

start_link(mod, fun, args)

Specs

start_link(module, atom, [term]) :: {:ok, pid}

Starts a task as part of a supervision tree.