協調器函式程式碼條件約束
Durable Functions 是 Azure Functions 的延伸模組,可讓您建置可設定狀況的應用程式。 您可以使用協調器函式來協調函數應用程式中其他 Durable Functions 的執行。 協調器函式可設定狀況、可靠且可能長時間執行。
協調器程式碼條件約束
協調器函式會使用事件來源來確保可靠的執行,以及維護區域變數狀態。 協調器程式碼的重新執行行為會建立可以在協調器函式中撰寫的程式碼類型其條件約束。 例如,協調器函式必須「具決定性」:協調器函式將會重新執行多次,且每次都必須產生相同的結果。
使用具決定性的 API
本節提供一些簡單的指導方針,可協助確保您的程式碼具決定性。
協調器函式可以使用其目標語言呼叫任何 API。 然而,協調器函式僅呼叫具決定性的 API 相當重要。 「具決定性的 API」是一種 API,無論呼叫的時機或頻率為何,都一律會傳回相同輸入所提供的相同值。
下列各節提供您應該避免之 API 與模式的指引,因為其「不」具決定性。 這些條件約束僅適用於協調器函式。 其他函式類型沒有此類條件約束。
注意
以下描述數種程式碼條件約束的類型。 可惜此清單並不完整,且可能未涵蓋某些使用案例。 撰寫協調器程式碼時,要考慮的最重要事項是您正在使用的 API 是否具決定性。 當您習慣以此方式思考之後,很容易就能了解可以安全地使用哪些 API,而不需要參考此記錄的清單。
日期和時間
傳回目前日期或時間的 API 不具決定性,且絕不應該用於協調器函式。 這是因為每次重新執行協調器函式都會產生不同的值。 您應該改用與 Durable Functions 相等的 API 來取得目前的日期或時間,這些日期或時間在重新執行時會維持一致。
請勿使用 DateTime.Now
、DateTime.UtcNow
或相等的 API 來取得目前時間。 也應避免 Stopwatch
等類別。 針對 .NET 同處理序協調器函式,請使用 IDurableOrchestrationContext.CurrentUtcDateTime
屬性來取得目前時間。 針對 .NET 隔離式協調器函式,請使用 TaskOrchestrationContext.CurrentDateTimeUtc
屬性來取得目前時間。
DateTime startTime = context.CurrentUtcDateTime;
// do some work
TimeSpan totalTime = context.CurrentUtcDateTime.Subtract(startTime);
GUID 與 UUID
傳回隨機 GUID 或 UUID 的 API 不具決定性,因為每次重新執行所產生的值都不同。 依據您使用的語言,內建 API 可用於產生具決定性的 GUID 或 UUID。 否則,請使用活動函式傳回隨機產生的 GUID 或 UUID。
請勿使用 Guid.NewGuid()
之類的 API 來產生隨機 GUID。 請改用內容物件的 NewGuid()
API 來產生能安全重新執行協調器的隨機 GUID。
Guid randomGuid = context.NewGuid();
注意
使用協調流程內容 API 所產生的 GUID 是類型 5 UUID。
隨機數字
使用活動函式將亂數傳回協調器函式。 活動函式的傳回值一律能安全重新執行,因為會將這些值儲存至協調流程歷程記錄中。
或者,具有固定種子值的亂數產生器可直接用於協調器函式中。 只要為每次協調流程重新執行產生相同的數字序列,此方法就很安全。
繫結
協調器函式不得使用任何繫結,包括協調流程用戶端與實體用戶端繫結。 一律使用用戶端或活動函式中的輸入與輸出繫結。 這很重要,因為協調器函式可能會多次重新執行,導致外部系統不具決定性且有重複的 I/O。
靜態變數
避免在協調器函式中使用靜態變數,因為其值可能會隨著時間而變更,產生不具決定性的執行階段行為。 請改用常數,或限制靜態變數使用活動函式。
注意
即使在協調器函式之外,使用 Azure Functions 中的靜態變數可能會因為各種原因而產生問題,因為不保證靜態狀態將在多個函式執行中持續存在。 除了非常特定的使用案例之外,均應避免靜態變數,例如盡可能在活動或實體函式中使用記憶體內部快取。
環境變數
請勿在協調器函式中使用環境變數。 其值可能會隨著時間而變更,產生不具決定性的執行階段行為。 若協調器函式需要環境變數中所定義的設定,您必須將設定值傳遞至協調器函式,作為輸入或活動函式的傳回值。
網路與 HTTP
使用活動函式進行輸出網路呼叫。 若您必須從協調器函式進行 HTTP 呼叫,也可以使用長期 HTTP API。
封鎖執行緒的 API
封鎖「睡眠」之類的 API 可能會導致協調器函式的效能與調整問題,並應避免。 在 Azure Functions 使用情況方案中,這些 API 甚至可能會產生不必要的執行時間費用。 當有替代方案可供使用時,請使用替代方案來封鎖 API。 例如,使用長期計時器建立可安全重新執行的延遲,且不會計入協調器函式的執行時間。
非同步 API
協調器程式碼絕對不能啟動任何非同步作業,但協調流程觸發程序的內容物件所定義作業除外。 例如,絕對不能使用 .NET 中的 Task.Run
、Task.Delay
與 HttpClient.SendAsync
,或 JavaScript 中的 setTimeout
與 setInterval
。 協調器函式應該只使用 Durable SDK API 排程非同步工作,例如排程活動函式。 任何其他非同步叫用類型都應該在活動函式內完成。
非同步 JavaScript 函式
一律將 JavaScript 協調器函式宣告為同步產生器函式。 您不得將 JavaScript 協調器函式宣告為 async
,因為 Node.js 執行階段不保證非同步函式具決定性。
Python 協同程式
您不得將 Python 協調器函式宣告為協同程式。 換句話說,絕對不要使用 async
關鍵字宣告 Python 協調器函式,因為協同程式語意與 Durable Functions 重新執行模型不一致。 您必須一律將 Python 協調器函式宣告為產生器,這表示您應該預期 context
API 會使用 yield
,而不是 await
。
.NET 執行緒 API
Durable Task Framework 會在單一執行緒上執行協調器程式碼,且無法與任何其他執行緒互動。 在協調流程執行的背景工作集區執行緒上執行非同步接續可能會產生不具決定性的執行或死結。 基於此原因,協調器函式應該幾乎不會使用執行緒 API。 例如,一律不要在協調器函式中使用 ConfigureAwait(continueOnCapturedContext: false)
。 這可確保工作接續會在協調器函式的原始 SynchronizationContext
上執行。
注意
Durable Task Framework 會嘗試偵測協調器函式中是否意外使用非協調器執行緒。 若發現違規,架構會擲回 NonDeterministicOrchestrationException 例外狀況。 然而,此偵測行為無法攔截所有違規,不應該依賴此偵測行為。
版本控制
長期協調流程可能會持續執行數天、數月、數年,或甚至永遠。 對 Durable Functions 應用程式進行會影響未完成協調流程的任何程式碼更新,都可能會中斷協調流程的重新執行行為。 這就是為什麼在對程式碼進行更新時,必須要謹慎規劃。 如需如何設定程式碼版本的詳細描述,請參閱版本設定文章。
長期工作
注意
本章節描述長期工作架構的內部實作詳細資料。 您不需要知道此資訊就可以使用長期函式。 它只是用來協助您了解重新執行行為。
可以在協調器函式中安全地等候的工作偶爾會稱為「長期工作」。 Durable Task Framework 會建立及管理這些工作。 範例是由 .NET 協調器函式中 CallActivityAsync
、WaitForExternalEvent
與 CreateTimer
傳回的工作。
這些長期工作是藉由 .NET 中 TaskCompletionSource
物件的清單來進行內部管理。 重新執行期間,會將這些工作建立為協調器程式碼執行的一部分。 當發送器列舉對應的歷程記錄事件時,就會完成這些工作。
這些工作會在已重新執行所有歷程記錄之前,使用單一執行緒以同步方式執行。 歷程記錄重新執行結束時未完成的長期工作都有適當的執行動作。例如,訊息可能會加入佇列以呼叫活動函式。
本節的執行階段行為描述應該可協助您了解協調器函式無法使用非長期工作中 await
或 yield
的原因。 有兩個原因:發送器執行緒無法等候工作完成,而該工作的任何回撥都可能會損毀協調器函式的追蹤狀態。 某些執行階段檢查可適當地協助偵測這些違規。
若要深入了解 Durable Task Framework 如何執行協調器函式,請參閱 GitHub 上的長期工作原始程式碼。 請特別參閱 TaskOrchestrationExecutor.cs 與 TaskOrchestrationContext.cs。