如何等待Elixir中的多个任务?

时间:2017-02-19 17:27:46

标签: erlang async-await task elixir

我想同时执行多个任务。在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 Task模块中实现Promise.all? 来自documentation,似乎只能await 1个任务;在每个task内定义1个函数;并使用async_stream仅将相同的函数应用于多个项目。

3 个答案:

答案 0 :(得分:12)

您可以将await函数映射到任务引用列表。 像

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

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

答案 1 :(得分:1)

自从提出这个问题后,Elixir 的任务模块就萌发了新的力量。

Task.await_many/2Task.yield_many/2,它们听起来像。

回答原始问题中的示例:

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])

没有与 Promise.any 类似的东西,但您可以使用 Task.yield_many/2

轻松编写一个

答案 2 :(得分:0)

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可以解决问题,但是如果您担心超时的话,可能会惹上麻烦。跨列表的映射是按顺序进行的,这意味着每个Task.await将在给出结果之前最多阻塞指定的超时,这时我们移至列表中的下一项并再次阻塞 直到整个超时。

我们可以通过创建一个睡眠2-10秒的任务列表来演示此行为。默认超时时间为5秒,当直接用await调用时,其中一些任务将被杀死,但是当我们在列表中枚举时,不会发生这种情况:

1..5
|> Enum.map(fn i -> Task.async(fn -> Process.sleep(i * 2000); i end) end)
|> Enum.map(&Task.await/1)

# Blocks for 10 seconds
# => [1, 2, 3, 4, 5]

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

1..5
|> Enum.map(fn i -> Task.async(fn -> Process.sleep(i * 2000); i end) end)
|> Task.yield_many(5000)
|> Enum.map(fn {t, res} -> res || Task.shutdown(t, :brutal_kill) end)

# Blocks for 5 seconds
# => [{:ok, 1}, {:ok, 2}, nil, nil, nil]