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