I am trying to run multiple long-running mix tasks asynchronously under a single parent mix task. I tried adding the child mix tasks as Task
s to a Supervisor
, but this doesn’t seem to work and the mix task exits immediately.
defmodule Mix.Tasks.MyApp.Server do
use Mix.Task
def run(_args) do
children = [
{Task, fn -> Mix.shell.cmd "cd assets && npm run start" end},
{Task, fn -> Mix.Task.run "phx.server" end}
]
Supervisor.init(children, strategy: :one_for_one)
end
end
Is there a good way to run long-running mix tasks asynchronously under a single parent mix task?
Spawning background threads won’t prevent the process from exiting - that will happen when the last statement in run
exits - so what you need to do is wait for that to happen. You were on the right lines. I think simplest thing is just create some tasks and await them all:
tasks = [
Task.async(fn -> Mix.Task.run "my task" end),
Task.async(fn -> Mix.Task.run "my task2" end),
]
Enum.map(tasks, &Task.await/1)
I would think using Task.yield_many is a better option because all Task.await
calls will sequentially wait for each task, one by one, while Task.yield_many
does not mandate an order in which the tasks will be executed.
EDIT: full code is always easier to read:
[
Task.async(fn -> Mix.Task.run "my task" end),
Task.async(fn -> Mix.Task.run "my task2" end),
]
|> Task.yield_many
Thanks for the quick response!
This solution works perfectly except that the default timeout for Task.await/1
is 5 seconds, so I added the :infinity
atom to keep the tasks running indefinitely.
tasks = [
Task.async(fn -> Mix.Task.run "my task" end),
Task.async(fn -> Mix.Task.run "my task2" end),
]
Enum.map(tasks, &Task.await(&1, :infinity))
2 Likes
All the tasks will start immediately, awaiting them in order is fine since he needs them all complete before he exits, it will take exactly as long as the slowest task. yield_many
is for when you need timeouts and want to get the first results, like a server processing in a loop.