erlang 如何在Elixir中等待多个任务?

nom7f22z  于 2022-12-08  发布在  Erlang
关注(0)|答案(3)|浏览(187)

我想同时执行多个任务。在Javascript中,我会:

async function cook_an_egg() {}

async function take_shower() {}

async function call_mum() {}

await Promise.all([cook_an_egg(), take_shower(), call_mum()])

如何在Elixir任务模块中实现Promise.all?从文档中看,似乎只能实现await 1个任务;在每个task内定义1个函数;并且仅将相同的函数应用于具有async_stream的多个项。

np8igboo

np8igboo1#

适用于Elixir v1.11.0及更高版本

Task.await_many的设计就是为了完成这个任务。它正确地处理了整个超时,并且在遇到退出、超时等情况时,应该做最不令人惊讶的事情。

tasks = [
  Task.async(fn -> cook_an_egg(:medium) end),
  Task.async(fn -> take_shower(10) end),
  Task.async(fn -> call_mum() end),
]

Task.await_many(tasks)

对于旧版本

Task.yield_many是一个比Task.await更安全的解决方案。不幸的是,它有点冗长,因为它让我们自己负责处理超时和死任务。如果我们想模仿async/await的行为,并在出错时退出,它将如下所示:

tasks = [
  Task.async(fn -> cook_an_egg(:medium) end),
  Task.async(fn -> take_shower(10) end),
  Task.async(fn -> call_mum() end),
]

Task.yield_many(tasks)
|> Enum.map(fn {task, result} ->
  case result do
    nil ->
      Task.shutdown(task, :brutal_kill)
      exit(:timeout)
    {:exit, reason} ->
      exit(reason)
    {:ok, result} ->
      result
  end
end)

为什么不使用await

使用Task.await可以在简单的情况下工作,但是如果你关心超时,你可能会给自己带来麻烦。列表中的Map是顺序发生的,这意味着每个Task.await在给出结果之前都会阻塞指定的超时时间,此时我们移动到列表中的下一个项目,并 * 再次 * 阻塞,直到完全超时。
我们可以通过创建一个睡眠时间为1-8秒的任务列表来演示这种行为。在默认的超时时间为5秒的情况下,当直接使用await调用这些任务时,其中一些任务会被杀死,但是当我们枚举列表时,情况并不是这样的:

for ms <- [2_000, 4_000, 6_000] do
  Task.async(fn -> Process.sleep(ms); ms end)
end
|> Enum.map(&Task.await/1)

# Blocks for 6 seconds
# => [2000, 4000, 6000]

# Each `await` picks up after the previous one finishes with a fresh 5s timeout.
# Since each one blocks for 2s before finishing, no timeout is triggered
# but the total run time runs over.
 
# async(2s)--await(2s)-->(2s)
# async(4s)                  --await(2s)-->(4s)
# async(6s)                                    --await(2s)-->(6s)

如果我们修改它以使用Task.yield_many,我们可以得到所需的行为:

for ms <- [2_000, 4_000, 6_000] do
  Task.async(fn -> Process.sleep(ms); ms end)
end
|> Task.yield_many(5000)
|> Enum.map(fn {t, res} -> res || Task.shutdown(t, :brutal_kill) end)

# Blocks for 5 seconds
# => [{:ok, 2000}, {:ok, 4000}, nil]
gg58donl

gg58donl2#

可以将await函数Map到任务引用列表。

tasks = Enum.reduce(0..9, [], fn _, acc -> 
  [Task.async(&any_job/0) | acc]
end)

Enum.map(tasks, &Task.await/1)
vqlkdk9b

vqlkdk9b3#

Since this question was asked, Elixir's Task module has sprouted new powers.
There is both Task.await_many/2 and Task.yield_many/2, which do what they sound like.
To answer the example in the original question:

cook_an_egg = Task.async(fn -> end)
take_shower = Task.async(fn -> end)
call_mum = Task.async(fn -> end)
Task.await_many([cook_an_egg, take_shower, call_mum])

There is no analog to Promise.any , but you could write one pretty easily using Task.yield_many/2

相关问题