Mix.install([
{:jason, "~> 1.4"},
{:kino, "~> 0.9", override: true},
{:youtube, github: "brooklinjazz/youtube"},
{:hidden_cell, github: "brooklinjazz/hidden_cell"}
])
- Why might we want to supervise a task?
- How do we supervise tasks?
We can start Task processes under a Task.Supervisor
which can dynamically supervise tasks. The supervisor will automatically restart tasks if they encounter an error.
Generally speaking, we should start Task processes under a supervisor.
We encourage developers to rely on supervised tasks as much as possible. Supervised tasks improves the visibility of how many tasks are running at a given moment and enable a huge variety of patterns that gives you explicit control on how to handle the results, errors, and timeouts. Here is a summary:
Using
Task.Supervisor.start_child/2
allows you to start a fire-and-forget task that you don't care about its results or if it completes successfully or not.Using
Task.Supervisor.async/2 + Task.await/2
allows you to execute tasks concurrently and retrieve its result. If the task fails, the caller will also fail.Using
Task.Supervisor.async_nolink/2
+ Task.yield/2 + Task.shutdown/2 allows you to execute tasks concurrently and retrieve their results or the reason they failed within a given time frame. If the task fails, the caller won't fail. You will receive the error reason either on yield or shutdown.
The Task.Supervisor
is started as a child under a normal supervisor. We start the Task.Supervisor
as a named process using an atom or a module name.
children = [
{Task.Supervisor, name: MyTaskSupervisor}
]
{:ok, supervisor_pid} =
Supervisor.start_link(children, strategy: :one_for_one, name: :parent_supervisor)
We don't need to define a MyTaskSupervisor
module. The supervisor uses this name to start a Task.Supervisor
process. We can see that MyTaskSupervisor
is a child process of our supervisor.
Supervisor.which_children(supervisor_pid)
We can also see that demonstrated in the following diagram.
Kino.Process.sup_tree(supervisor_pid)
Now we can spawn supervised Task processes under MyTaskSupervisor
using Task.Supervisor.async/3
.
task =
Task.Supervisor.async(MyTaskSupervisor, fn ->
IO.puts("Task Started")
Process.sleep(1000)
IO.puts("Task Finished")
"response!"
end)
Task.await(task)
We can spawn many tasks under the supervisor.
tasks =
Enum.map(1..5, fn int ->
task =
Task.Supervisor.async(MyTaskSupervisor, fn ->
IO.puts("Task Started")
Process.sleep(30000)
IO.puts("Task Finished")
int * 2
end)
end)
Evaluate the diagram below to see the tasks spawned under the MyTaskSupervisor
process.
Kino.Process.sup_tree(supervisor_pid)
We can then await the response from all of the tasks.
Task.await_many(tasks)
Task.Supervisor.start_child/2
allows us to start a fire-and-forget task that will perform some work without returning a response.
Task.Supervisor.start_child(MyTaskSupervisor, fn ->
IO.puts("Fire-and-forget task started")
Process.sleep(60000)
IO.puts("Fire-and-forget task finished")
end)
Re-evaluate the cell above a few times, and you'll see several tasks under the MyTaskSupervisor
.
children = Supervisor.which_children(MyTaskSupervisor)
We can provide a :restart
strategy when we start a process. By default, Task.Supervisor.start_child/2
uses the :temporary
:restart
strategy. These Task processes will never be restarted.
{:ok, pid} =
Task.Supervisor.start_child(MyTaskSupervisor, fn ->
Process.sleep(60000)
end)
Supervisor.which_children(MyTaskSupervisor)
|> IO.inspect(label: "Started children")
Process.exit(pid, :kill)
Process.sleep(1000)
Supervisor.which_children(MyTaskSupervisor)
|> IO.inspect(label: "Children after exit")
Instead we can use the :permanent
process to always restart a Task or :transient
to restart a Task if it's exit reason is not :normal
, :shutdown
, or {:shutdown, reason}
.
See Task.Supervisor.start_child/3#options for more.
Now when we kill a Task started with the :transient
strategy, notice that a new process with a different pid
is started under MyTaskSupervisor
.
{:ok, pid} =
Task.Supervisor.start_child(
MyTaskSupervisor,
fn ->
Process.sleep(60000)
end,
restart: :transient
)
Supervisor.which_children(MyTaskSupervisor)
|> IO.inspect(label: "Started children")
Process.exit(pid, :kill)
Process.sleep(1000)
Supervisor.which_children(MyTaskSupervisor)
|> IO.inspect(label: "Children after exit")
Consider the following resource(s) to deepen your understanding of the topic.
DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.
Run git status
to ensure there are no undesirable changes.
Then run the following in your command line from the curriculum
folder to commit your progress.
$ git add .
$ git commit -m "finish Task Supervisor reading"
$ git push
We're proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.
We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.