Elixir语言中 Task 相关用法介绍如下。
产生和等待任务的便利。
任务是旨在在其整个生命周期中执行一项特定操作的进程,通常与其他进程很少或根本没有通信。任务最常见的用例是通过异步计算值将顺序代码转换为并发代码:
task = Task.async(fn -> do_some_work() end)
res = do_some_other_work()
res + Task.await(task)
使用async 生成的任务可以由它们的调用者进程(并且只有它们的调用者)等待,如上面的示例所示。它们是通过生成一个进程来实现的,该进程在执行给定的计算后向调用者发送消息。
除了 和 async/1 之外,任务还可以作为监督树的一部分启动并在远程节点上动态生成。接下来我们将探讨这些场景。await/2
异步和等待
任务的常见用途之一是使用 将顺序代码转换为并发代码,同时保持其语义。调用时,调用者将创建、链接和监视一个新进程。任务操作完成后,将向调用者发送一条带有结果的消息。Task.async/1
用于读取任务发送的消息。Task.await/2
使用 async 时需要考虑两件重要的事情:
-
如果您使用的是异步任务,则必须等待回复,因为它们是
always发送的。如果您不期待回复,请考虑使用,详情如下。Task.start_link/1 -
异步任务链接调用者和生成的进程。这意味着,如果调用者崩溃,任务也会崩溃,反之亦然。这是有目的的:如果要接收结果的过程不再存在,那么完成计算就没有目的了。
如果不希望这样做,您将需要使用监督任务,如下所述。
动态监督任务
模块允许开发人员动态创建多个监督任务。Task.Supervisor
一个简短的例子是:
{:ok, pid} = Task.Supervisor.start_link()
task =
Task.Supervisor.async(pid, fn ->
# Do something
end)
Task.await(task)
但是,在大多数情况下,您希望将任务主管添加到您的监督树中:
Supervisor.start_link([
{Task.Supervisor, name: MyApp.TaskSupervisor}
], strategy: :one_for_one)
现在您可以通过传递主管的名称而不是 pid 来使用 async/await:
Task.Supervisor.async(MyApp.TaskSupervisor, fn ->
# Do something
end)
|> Task.await()
我们鼓励开发人员尽可能多地依赖受监督的任务。受监督的任务支持多种模式,允许您明确控制如何处理结果、错误和超时。这是一个摘要:
-
使用
允许您启动一个您不关心其结果或它是否成功完成的fire-and-forget 任务。Task.Supervisor.start_child/2 -
使用
+Task.Supervisor.async/2允许您同时执行任务并检索其结果。如果任务失败,调用者也会失败。Task.await/2 -
使用
+Task.Supervisor.async_nolink/2+Task.yield/2允许您同时执行任务并检索它们的结果或它们在给定时间范围内失败的原因。如果任务失败,调用者不会失败。您将在Task.shutdown/2yield或shutdown上收到错误原因。
有关支持的操作的详细信息,请参阅 模块。Task.Supervisor
分布式任务
由于 Elixir 提供了 ,因此很容易使用它来跨节点动态启动任务:Task.Supervisor
# On the remote node
Task.Supervisor.start_link(name: MyApp.DistSupervisor)
# On the client
supervisor = {MyApp.DistSupervisor, :remote@local}
Task.Supervisor.async(supervisor, MyMod, :my_fun, [arg1, arg2, arg3])
请注意,在处理分布式任务时,应该使用需要显式模块、函数和参数的 函数,而不是使用匿名函数的Task.Supervisor.async/5 。这是因为匿名函数期望所有涉及的节点上都存在相同的模块版本。查看Task.Supervisor.async/3 模块文档以获取有关分布式进程的更多信息,因为那里说明的限制适用于整个生态系统。Agent
静态监督任务
模块实现了 Task 函数,这允许它直接在常规 child_spec/1 下启动 - 而不是 Supervisor - 通过传递带有要运行的函数的元组:Task.Supervisor
Supervisor.start_link([
{Task, fn -> :some_work end}
], strategy: :one_for_one)
当您需要在设置监督树时执行某些步骤时,这通常很有用。例如:预热缓存、记录初始化状态等。
如果您不想将任务代码直接放在 下,可以将 Supervisor 包装在其自己的模块中,类似于使用 Task 或 GenServer 的方式:Agent
defmodule MyTask do
use Task
def start_link(arg) do
Task.start_link(__MODULE__, :run, [arg])
end
def run(arg) do
# ...
end
end
然后将其传递给主管:
Supervisor.start_link([
{MyTask, arg}
], strategy: :one_for_one)
由于这些任务是受监督的,并且不直接与调用者相关联,因此无法等待它们。默认情况下,函数 和Task.start/1 用于fire-and-forget 任务,您不关心结果或它是否成功完成。Task.start_link/1
use Task 定义了一个 函数,允许将定义的模块放在监督树下。生成的child_spec/1 可以使用以下选项进行自定义:child_spec/1
:id- 子规范标识符,默认为当前模块:restart- 当孩子应该重新启动时,默认为:temporary:shutdown- 如何立即关闭孩子,或者让孩子有时间关闭
与 、 GenServer 和 Agent 相对,任务的默认 Supervisor :restart 为 :temporary 。这意味着即使任务崩溃也不会重新启动任务。如果您希望在不成功退出时重新启动任务,请执行以下操作:
use Task, restart: :transient
如果您希望任务始终重新启动:
use Task, restart: :permanent
有关更多详细信息,请参阅 模块中的"Child specification" 部分。紧接在Supervisor use Task 之前的@doc 注释将附加到生成的 函数。child_spec/1
祖先和调用者跟踪
每当您启动一个新进程时,Elixir 都会通过进程字典中的 $ancestors 键对该进程的父进程进行注释。这通常用于跟踪监督树内的层次结构。
例如,我们建议开发人员始终在主管的指导下启动任务。这提供了更多可见性,并允许您控制在节点关闭时如何终止这些任务。这可能看起来像Task.Supervisor.start_child(MySupervisor, task_function).这意味着,尽管您的代码是调用任务的人,但任务的实际祖先是主管,因为主管是有效启动它的人。
为了跟踪您的代码和任务之间的关系,我们使用进程字典中的$callers 键。因此,假设上面的 调用,我们有:Task.Supervisor
[your code] -- calls --> [supervisor] ---- spawns --> [task]
这意味着我们存储以下关系:
[your code] [supervisor] <-- ancestor -- [task]
^ |
|--------------------- caller ---------------------|
可以使用 Process.get(:"$callers") 从 Process 字典中检索当前进程的调用者列表。这将返回 nil 或带有至少一个条目的列表 [pid_n, ..., pid2, pid1] 其中 pid_n 是调用当前进程的 PID,pid2 调用 pid_n 和 pid2 由 pid1 调用。
如果任务崩溃,调用者字段将作为日志消息元数据的一部分包含在 :callers 键下。
相关用法
- Elixir Task.yield_many用法及代码示例
- Elixir Task.Supervisor.async_stream用法及代码示例
- Elixir Task.async用法及代码示例
- Elixir Task.await_many用法及代码示例
- Elixir Task.Supervisor用法及代码示例
- Elixir Task.completed用法及代码示例
- Elixir Task.Supervisor.start_child用法及代码示例
- Elixir Task.yield用法及代码示例
- Elixir Task.async_stream用法及代码示例
- Elixir Task.Supervisor.async_nolink用法及代码示例
- Elixir Task.Supervisor.start_link用法及代码示例
- Elixir Task.await用法及代码示例
- Elixir Time.add用法及代码示例
- Elixir Time.new用法及代码示例
- Elixir Tuple.duplicate用法及代码示例
- Elixir Time.to_erl用法及代码示例
- Elixir Tuple用法及代码示例
- Elixir Time.utc_now用法及代码示例
- Elixir Tuple.sum用法及代码示例
- Elixir Time.to_iso8601用法及代码示例
- Elixir Time.from_iso8601用法及代码示例
- Elixir Time.from_erl!用法及代码示例
- Elixir Time.from_seconds_after_midnight用法及代码示例
- Elixir Tuple.product用法及代码示例
- Elixir Time.truncate用法及代码示例
注:本文由纯净天空筛选整理自elixir-lang.org大神的英文原创作品 Task。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。
