Task.await

You're seeing just the function await, go back to Task module for more information.
Link to this function

await(task, timeout \\ 5000)

View Source

Specs

await(t(), timeout()) :: term()

Awaits a task reply and returns it.

In case the task process dies, the current process will exit with the same reason as the task.

A timeout, in milliseconds or :infinity, can be given with a default value of 5000. If the timeout is exceeded, then the current process will exit. If the task process is linked to the current process which is the case when a task is started with async, then the task process will also exit. If the task process is trapping exits or not linked to the current process, then it will continue to run.

This function assumes the task's monitor is still active or the monitor's :DOWN message is in the message queue. If it has been demonitored, or the message already received, this function will wait for the duration of the timeout awaiting the message.

This function can only be called once for any given task. If you want to be able to check multiple times if a long-running task has finished its computation, use yield/2 instead.

Examples

iex> task = Task.async(fn -> 1 + 1 end)
iex> Task.await(task)
2

Compatibility with OTP behaviours

It is not recommended to await a long-running task inside an OTP behaviour such as GenServer. Instead, you should match on the message coming from a task inside your GenServer.handle_info/2 callback.

A GenServer will receive two messages on handle_info/2:

  • {ref, result} - the reply message where ref is the monitor reference returned by the task.ref and result is the task result

  • {:DOWN, ref, :process, pid, reason} - since all tasks are also monitored, you will also receive the :DOWN message delivered by Process.monitor/1. If you receive the :DOWN message without a a reply, it means the task crashed

Another consideration to have in mind is that tasks started by Task.async/1 are always linked to their callers and you may not want the GenServer to crash if the task crashes. Therefore, it is preferable to instead use Task.Supervisor.async_nolink/3 inside OTP behaviours. For completeness, here is an example of a GenServer that start tasks and handles their results:

defmodule GenServerTaskExample do
  use GenServer

  def start_link(opts) do
    GenServer.start_link(__MODULE__, :ok, opts)
  end

  def init(_opts) do
    # We will keep all running tasks in a map
    {:ok, %{tasks: %{}}}
  end

  # Imagine we invoke a task from the GenServer to access a URL...
  def handle_call(:some_message, _from, state) do
    url = ...
    task = Task.Supervisor.async_nolink(MyApp.TaskSupervisor, fn -> fetch_url(url) end)

    # After we start the task, we store its reference and the url it is fetching
    state = put_in(state.tasks[task.ref], url)

    {:reply, :ok, state}
  end

  # If the task succeeds...
  def handle_info({ref, result}, state) do
    # The task succeed so we can cancel the monitoring and discard the DOWN message
    Process.demonitor(ref, [:flush])

    {url, state} = pop_in(state.tasks[ref])
    IO.puts "Got #{inspect(result)} for URL #{inspect url}"
    {:noreply, state}
  end

  # If the task fails...
  def handle_info({:DOWN, ref, _, _, reason}, state) do
    {url, state} = pop_in(state.tasks[ref])
    IO.puts "URL #{inspect url} failed with reason #{inspect(reason)}"
    {:noreply, state}
  end
end

With the server defined, you will want to start the task supervisor above and the GenServer in your supervision tree:

children = [
  {Task.Supervisor, name: MyApp.TaskSupervisor},
  {GenServerTaskExample, name: MyApp.GenServerTaskExample}
]

Supervisor.start_link(children, strategy: :one_for_one)