当前位置: 首页>>代码示例 >>用法及示例精选 >>正文


Elixir Task.async_stream用法及代码示例


Elixir语言中 Task.async_stream 相关用法介绍如下。

用法一

async_stream(enumerable, fun, options \\ [])
(从 1.4.0 开始)
@spec async_stream(Enumerable.t(), (term() -> term()), keyword()) :: Enumerable.t()

返回在 enumerable 中的每个元素上同时运行给定函数 fun 的流。

async_stream/5 工作方式相同,但使用匿名函数而不是 module-function-arguments 元组。 fun 必须是 one-arity 匿名函数。

每个 enumerable 元素作为参数传递给给定的函数 fun 并由其自己的任务处理。任务将链接到调用者进程,类似于 async/1

示例

异步计算每个字符串中的代码点,然后使用 reduce 将计数相加。

iex> strings = ["long string", "longer string", "there are many of these"]
iex> stream = Task.async_stream(strings, fn text -> text |> String.codepoints() |> Enum.count() end)
iex> Enum.reduce(stream, 0, fn {:ok, num}, acc -> num + acc end)
47

有关讨论、选项和更多示例,请参阅 async_stream/5

用法二

async_stream(enumerable, module, function_name, args, options \\ [])
(从 1.4.0 开始)
@spec async_stream(Enumerable.t(), module(), atom(), [term()], keyword()) ::
  Enumerable.t()

返回一个流,其中给定函数(modulefunction_name)同时映射到 enumerable 中的每个元素上。

enumerable 的每个元素都将附加到给定的 args 并由其自己的任务处理。这些任务将链接到一个中间流程,然后该流程链接到调用者流程。这意味着任务中的失败会终止调用者进程,而调用者进程中的失败会终止所有任务。

流式传输时,每个任务将在成功完成时发出{:ok, value},如果调用者正在捕获退出,则发出{:exit, reason}。结果的顺序取决于:ordered 选项的值。

并发级别和允许任务运行的时间可以通过选项来控制(参见下面的"Options" 部分)。

考虑使用 Task.Supervisor.async_stream/6 在主管下启动任务。如果您发现自己捕获出口以确保任务中的错误不会终止调用者进程,请考虑使用 Task.Supervisor.async_stream_nolink/6 来启动未链接到调用者进程的任务。

选项

  • :max_concurrency - 设置同时运行的最大任务数。默认为 System.schedulers_online/0

  • :ordered - 结果是否应以与输入流相同的顺序返回。当输出被排序时,Elixir 可能需要缓冲结果以按原始顺序发出它们。将此选项设置为 false 将禁用以删除排序为代价进行缓冲的需要。当您仅将任务用于副作用时,这也很有用。请注意,无论:ordered 设置为什么,任务都将异步处理。如果您需要按顺序处理元素,请考虑改用 Enum.map/2 Enum.each/2 。默认为 true

  • :timeout - 每个任务允许执行的最长时间(以毫秒或 :infinity 为单位)。默认为 5000

  • :on_timeout - 任务超时时该怎么做。可能的值是:

    • :exit(默认)- 调用者(产生任务的进程)退出。
    • :kill_task - 超时的任务被杀死。为该任务发出的值是 {:exit, :timeout}

示例

让我们构建一个流,然后枚举它:

stream = Task.async_stream(collection, Mod, :expensive_fun, [])
Enum.to_list(stream)

可以使用:max_concurrency 选项增加或减少并发性。例如,如果任务 IO 很重,则可以增加该值:

max_concurrency = System.schedulers_online() * 2
stream = Task.async_stream(collection, Mod, :expensive_fun, [], max_concurrency: max_concurrency)
Enum.to_list(stream)

如果您不关心计算结果,可以使用 Stream.run/1 运行流。还要设置 ordered: false ,因为您也不关心结果的顺序:

stream = Task.async_stream(collection, Mod, :expensive_fun, [], ordered: false)
Stream.run(stream)

第一个要完成的异步任务

也可以使用 async_stream/3 执行M个任务,找到N个任务完成。例如:

[
  &heavy_call_1/0,
  &heavy_call_2/0,
  &heavy_call_3/0
]
|> Task.async_stream(fn fun -> fun.() end, ordered: false, max_concurrency: 3)
|> Stream.filter(&match?({:ok, _}, &1))
|> Enum.take(2)

在上面的示例中,我们正在执行三个任务并等待前两个任务完成。我们使用 Stream.filter/2 将自己限制为仅成功完成的任务,然后使用 Enum.take/2 检索N 个项目。请注意,设置 ordered: falsemax_concurrency: M 很重要,其中 M 是任务数,以确保所有调用同时执行。

注意:未绑定 async + take

如果您想潜在地处理大量项目并仅保留部分结果,则可以end-up 处理比预期更多的项目。让我们看一个例子:

1..100
|> Task.async_stream(fn i ->
  Process.sleep(100)
  IO.puts(to_string(i))
end)
|> Enum.take(10)

在具有 8 个内核的机器上运行上面的示例将处理 16 个项目,即使您只需要 10 个元素,因为 async_stream/3 同时处理项目。那是因为它将一次处理 8 个元素。然后所有 8 个元素大致同时完成,导致 8 个元素被启动以进行处理。在这额外的 8 个中,只有 2 个将被使用,其余的将被终止。

根据问题,您可以预先过滤或限制元素的数量:

1..100
|> Stream.take(10)
|> Task.async_stream(fn i ->
  Process.sleep(100)
  IO.puts(to_string(i))
end)
|> Enum.to_list()

在其他情况下,您可能需要调整 :max_concurrency 以限制有多少元素可能被过度处理,但会降低并发性。您还可以将要采用的元素数设置为 :max_concurrency 的倍数。例如,在上面的示例中设置max_concurrency: 5

相关用法


注:本文由纯净天空筛选整理自elixir-lang.org大神的英文原创作品 Task.async_stream(enumerable, fun, options \\ [])。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。