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。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。
