Codeeinschränkungen für Orchestratorfunktionen

Durable Functions ist eine Erweiterung von Azure Functions, mit der Sie zustandsbehaftete Apps erstellen können. Mit einer Orchestratorfunktion können Sie die Ausführung anderer dauerhafter Funktionen innerhalb einer Funktions-App orchestrieren. Orchestratorfunktionen sind zustandsbehaftet, zuverlässig und können lange ausgeführt werden.

Einschränkungen des Orchestratorcodes

Orchestratorfunktionen nutzen Ereignissourcing, um eine zuverlässige Ausführung zu gewährleisten und den lokalen Variablenzustand beizubehalten. Mit dem Wiedergabeverhalten von Orchestratorcode sind Einschränkungen des Codetyps verbunden, der in einer Orchestratorfunktion geschrieben werden kann. Beispielsweise müssen Orchestratorfunktionen deterministisch sein: Eine Orchestratorfunktion wird mehrmals wiedergegeben und muss jedes Mal das gleiche Ergebnis liefern.

Verwenden deterministischer APIs

Dieser Abschnitt enthält einige einfache Richtlinien, die sicherstellen, dass Ihr Code deterministisch ist.

Orchestratorfunktionen können jede API in ihren Zielsprachen aufrufen. Es ist jedoch wichtig, dass Orchestratorfunktionen nur deterministische APIs aufrufen. Eine deterministische API ist eine API, die bei Verwendung der gleichen Eingabe immer den gleichen Wert zurückgibt, und zwar unabhängig davon, wann oder wie oft sie aufgerufen wird.

Die folgenden Abschnitte enthalten Leitfäden zu APIs und Mustern, die Sie vermeiden sollten, weil sie nicht deterministisch sind. Diese Einschränkungen gelten nur für Orchestratorfunktionen. Für andere Funktionstypen gelten keine Einschränkungen dieser Art.

Hinweis

Nachfolgend werden verschiedene Arten von Codeeinschränkungen beschrieben. Diese Liste ist leider nicht umfassend und einige Anwendungsfälle sind möglicherweise nicht abgedeckt. Das Wichtigste, was Sie beim Schreiben von Orchestratorcode berücksichtigen müssen, ist, ob die von Ihnen verwendete API deterministisch ist. Wenn Sie sich mit dieser Denkweise vertraut gemacht haben, ist es einfach zu verstehen, welche APIs sicher zu verwenden sind und welche nicht, ohne dass Sie auf diese dokumentierte Liste zurückgreifen müssen.

Datums- und Zeitangaben

APIs, die das aktuelle Datum oder die Uhrzeit zurückgeben, sind nicht deterministisch und sollten niemals in Orchestratorfunktionen verwendet werden. Das liegt daran, dass jede Wiedergabe einer Orchestratorfunktion einen anderen Wert erzeugt. Verwenden Sie stattdessen die entsprechende API von Durable Functions, um das aktuelle Datum oder die aktuelle Uhrzeit abzurufen, die über alle Wiedergaben hinweg konsistent bleibt.

Verwenden Sie nicht DateTime.Now, DateTime.UtcNow oder gleichwertige APIs, um die aktuelle Uhrzeit abzurufen. Klassen wie Stopwatch sollten ebenfalls vermieden werden. Verwenden Sie für .NET In-Process-Orchestratorfunktionen die IDurableOrchestrationContext.CurrentUtcDateTime Eigenschaft, um die aktuelle Uhrzeit abzurufen. Verwenden Sie für .NET isolierte Orchestratorfunktionen die TaskOrchestrationContext.CurrentDateTimeUtc Eigenschaft, um die aktuelle Uhrzeit abzurufen.

DateTime startTime = context.CurrentUtcDateTime;
// do some work
TimeSpan totalTime = context.CurrentUtcDateTime.Subtract(startTime);

GUIDs und UUIDs

APIs, die eine zufällige GUID oder UUID zurückgeben, sind nicht deterministisch, da der generierte Wert bei jeder Wiedergabe unterschiedlich ist. Je nachdem, welche Sprache Sie verwenden, ist möglicherweise eine integrierte API für die Generierung deterministischer GUIDs oder UUIDs verfügbar. Verwenden Sie andernfalls eine Aktivitätsfunktion, um eine zufällig generierte GUID oder UUID zurückzugeben.

Verwenden Sie keine APIs wie Guid.NewGuid(), um zufällige GUIDs zu generieren. Verwenden Sie stattdessen die NewGuid()-API des Kontextobjekts, um eine zufällige GUID zu generieren, die für die Wiedergabe durch den Orchestrator sicher ist.

Guid randomGuid = context.NewGuid();

Hinweis

GUIDs, die mit Orchestrierungskontext-APIs generiert werden, sind Typ 5 UUIDs.

Zufallszahlen

Verwenden Sie eine Aktivitätsfunktion, um Zufallszahlen an eine Orchestratorfunktion zurückzugeben. Die Rückgabewerte von Aktivitätsfunktionen sind immer sicher für die Wiedergabe, da sie in der Orchestrierungshistorie gespeichert werden.

Alternativ kann ein Zufallszahlengenerator mit einem festen Seedwert direkt in einer Orchestratorfunktion verwendet werden. Dieser Ansatz ist sicher, solange die gleiche Sequenz von Zahlen für jede Orchestrierungswiedergabe generiert wird.

Bindungen

Eine Orchestratorfunktion darf keine Bindungen verwenden, auch nicht die Bindungen des Orchestrierungsclients und des Entitätsclients. Verwenden Sie immer Eingabe- und Ausgabebindungen aus einer Client- oder Aktivitätsfunktion. Dies ist wichtig, da Orchestratorfunktionen möglicherweise mehrfach wiedergegeben werden, was zu nicht deterministischen und doppelten E/A mit externen Systemen führt.

Statische Variablen

Vermeiden Sie die Verwendung statischer Variablen in Orchestratorfunktionen, da sich deren Werte im Laufe der Zeit ändern können, was zu einem nicht-deterministischen Laufzeitverhalten führt. Verwenden Sie stattdessen Konstanten oder beschränken Sie die Verwendung von statischen Variablen auf Aktivitätsfunktionen.

Hinweis

Auch außerhalb von Orchestratorfunktionen kann die Verwendung statischer Variablen in Azure Functions aus verschiedenen Gründen problematisch sein, da es keine Garantie gibt, dass der statische Zustand über mehrere Funktionsausführungen hinweg beibehalten wird. Statische Variablen sollten vermieden werden, es sei denn, es handelt sich um sehr spezielle Anwendungsfälle, wie z.B. die bestmögliche Zwischenspeicherung im Speicher in Aktivitäts- oder Entitätsfunktionen.

Umgebungsvariablen

Verwenden Sie in Orchestratorfunktionen keine Umgebungsvariablen. Deren Werte können sich im Laufe der Zeit ändern, was zu einem nicht deterministischen Laufzeitverhalten führt. Wenn eine Orchestratorfunktion eine Konfiguration benötigt, die in einer Umgebungsvariable definiert ist, müssen Sie den Konfigurationswert als Eingabe oder als Rückgabewert einer Aktivitätsfunktion an die Orchestratorfunktion übergeben.

Netzwerk und HTTP

Verwenden Sie für ausgehende Netzwerkaufrufe Aktivitätsfunktionen. Wenn Sie in Ihrer Orchestratorfunktion einen HTTP-Aufruf ausführen müssen, können Sie auch dauerhafte HTTP-APIs verwenden.

Threadblockierungs-APIs

Blockierende APIs wie "sleep" können Leistungs- und Skalierungsprobleme für Orchestratorfunktionen verursachen und sollten daher vermieden werden. Im Azure Functions-Verbrauchstarif können sie sogar zu unnötigen Gebühren für die Ausführungszeit führen. Verwenden Sie Alternativen zum Blockieren von APIs, wenn diese verfügbar sind. Verwenden Sie beispielsweise dauerhafte Zeitgeber, um Verzögerungen zu erstellen, die für die Wiedergabe sicher sind und nicht auf die Ausführungszeit einer Orchestratorfunktion zählen.

Asynchrone APIs

Der Orchestratorcode darf niemals einen asynchronen Vorgang starten, mit Ausnahme derjenigen, die durch das Kontextobjekt des Orchestrationstriggers definiert sind. Beispielsweise können Sie nie Task.Run, Task.Delay oder HttpClient.SendAsync in .NET oder setTimeout und setInterval in JavaScript verwenden. Eine Orchestratorfunktion sollte nur asynchrone Arbeit mit dauerhaften SDK-APIs planen, z.B. Planungsaktivitätsfunktionen. Alle anderen asynchronen Aufrufe sollten innerhalb von Aktivitätsfunktionen ausgeführt werden.

Asynchrone JavaScript-Funktion

Deklarieren Sie JavaScript-Orchestratorfunktionen immer als synchrone Generatorfunktionen. Sie dürfen JavaScript-Orchestratorfunktionen nicht als async deklarieren, da die Node.js-Laufzeitumgebung nicht garantiert, dass asynchrone Funktionen deterministisch sind.

Python-Coroutinen

Sie dürfen keine Python-Orchestratorfunktionen als Coroutinen deklarieren. Mit anderen Worten, deklarieren Sie niemals Python Orchestratorfunktionen mit dem Schlüsselwort async, da die Coroutine-Semantik nicht mit dem Durable-Functions Wiedergabemodell übereinstimmt. Sie müssen Python Orchestratorfunktionen immer als Generatoren deklarieren, d.h. Sie sollten erwarten, dass die context API yield anstelle von await verwendet.

.NET Threading-APIs

Das Durable Task Framework führt Orchestratorcode über einen einzelnen Thread aus und kann nicht mit anderen Threads interagieren. Die Ausführung von asynchronen Fortsetzungen auf einem Worker-Pool-Thread einer Orchestrierung kann zu einer nicht-deterministischen Ausführung oder zu Deadlocks führen. Aus diesem Grund sollten Orchestratorfunktionen so gut wie nie Threading-APIs verwenden. Verwenden Sie beispielsweise niemals ConfigureAwait(continueOnCapturedContext: false) in einer Orchestratorfunktion. Dadurch wird sichergestellt, dass die Aufgabenfortsetzungen auf dem ursprünglichen SynchronizationContext der Orchestratorfunktion ausgeführt werden.

Hinweis

Das Durable Task Framework versucht, die versehentliche Verwendung von Nicht-Orchestrator-Threads in Orchestratorfunktionen zu erkennen. Wenn ein Verstoß gefunden wird, löst das Framework eine NonDeterministicOrchestrationException-Ausnahme aus. Dieses Erkennungsverhalten erfasst jedoch nicht alle Verstöße, und Sie sollten sich nicht darauf verlassen.

Versionsverwaltung

Eine dauerhafte Orchestrierung kann ununterbrochen über mehrere Tage, Monate oder Jahre hinweg oder sogar unendlich lange ausgeführt werden. Codeaktualisierungen für Durable Functions-Apps, die sich auf noch nicht abgeschlossene Orchestrierungen auswirken, können das Wiedergabeverhalten der Orchestrierungen beeinträchtigen. Daher müssen Codeaktualisierungen sorgfältig geplant werden. Eine ausführlichere Beschreibung der Versionsverwaltung für Ihren Code finden Sie im Artikel zur Versionsverwaltung.

Durable Tasks (Permanente Vorgänge)

Hinweis

In diesem Abschnitt werden Details der internen Implementierung für das Durable Task Framework beschrieben. Sie können Durable Functions nutzen, ohne diese Informationen zu kennen. Sie sollen Ihnen nur als Hilfe beim Verständnis des Wiedergabeverhaltens dienen.

Tasks, die in Orchestratorfunktionen auf sichere Weise warten können, werden auch als Durable Tasks bezeichnet. Das Durable Task Framework erstellt und verwaltet diese Tasks. Beispiele wären etwa die Aufgaben, die in .NET-Orchestratorfunktionen von CallActivityAsync, WaitForExternalEvent und CreateTimer zurückgegeben werden.

Diese dauerhaften Aufgaben werden intern mithilfe einer Liste mit Objekten vom Typ TaskCompletionSource in .NET verwaltet. Während der Wiedergabe werden diese Tasks im Rahmen der Orchestratorcodeausführung erstellt. Sie werden abgeschlossen, wenn der Verteiler die entsprechenden Verlaufsereignisse aufzählt.

Die Ausführung der Tasks erfolgt synchron mit einem einzelnen Thread, bis der gesamte Verlauf wiedergegeben wurde. Für alle Durable Tasks, die am Ende der Verlaufswiedergabe nicht abgeschlossen wurden, werden entsprechende Aktionen durchgeführt. Beispielsweise kann eine Nachricht in die Warteschlange eingereiht werden, um eine Aktivitätsfunktion aufzurufen.

Die Beschreibung des Laufzeitverhaltens in diesem Abschnitt soll Ihnen helfen zu verstehen, warum eine Orchestratorfunktion await oder yield nicht in einem Task verwenden kann, der kein Durable Task ist. Es gibt zwei Gründe: Der Verteilerthread kann nicht warten, bis der Task beendet ist, und jeder Rückruf durch diesen Task könnte den Nachverfolgungsstatus der Orchestratorfunktion möglicherweise beschädigen. Es stehen einige Laufzeitüberprüfungen zur Verfügung, mit denen versucht werden kann, diese Verstöße zu erkennen.

Weitere Informationen zum Ausführen von Orchestratorfunktionen durch das Durable Task Framework finden Sie im Artikel zum Durable Task-Quellcode auf GitHub. Sehen Sie sich vor allem TaskOrchestrationExecutor.cs und TaskOrchestrationContext.cs an.

Nächste Schritte

Versioning in Durable Functions (Azure Functions) (Versionsverwaltung in Durable Functions [Azure Functions])