Task.await
await
, go back to Task module for more information.
Specs
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 whereref
is the monitor reference returned by thetask.ref
andresult
is the task result{:DOWN, ref, :process, pid, reason}
- since all tasks are also monitored, you will also receive the:DOWN
message delivered byProcess.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)