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()
我们鼓励开发人员尽可能多地依赖受监督的任务。受监督的任务支持多种模式,允许您明确控制如何处理结果、错误和超时。这是一个摘要:
-
使用
Task.Supervisor.start_child/2
-
使用
Task.Supervisor.async/2
Task.await/2
-
使用
Task.Supervisor.async_nolink/2
Task.yield/2
Task.shutdown/2
yield
或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。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。