Freigeben über


Orchestrator-Funktionscodeeinschränkungen

Erstellen Sie zustandsbehaftete Apps mit Durable Functions. Es ist eine Erweiterung von Azure Functions. Verwenden Sie eine orchestrator-Funktion, um andere Durable Functions in Ihrer Funktions-App zu koordinieren. Orchestratorfunktionen sind zustandsbehaftet, zuverlässig und wurden für einen längeren Zeitraum konzipiert.

Erstellen Sie zustandsbehaftete, fehlertolerante Workflows mit den SdKs für dauerhafte Aufgaben in .NET, Python und Java. Verwenden Sie einen Orchestrator, um Aktivitäten und Unter-Orchestrierungen zu koordinieren. Orchestratoren sind zustandsbehaftet, zuverlässig und für eine lange Laufzeit ausgelegt.

Orchestrator-Codeeinschränkungen

Orchestratorfunktionen verwenden Event-Sourcing, um eine zuverlässige Ausführung zu gewährleisten und den Zustand lokaler Variablen aufrechtzuerhalten. Das Wiedergabeverhalten von Orchestratorcode erstellt Einschränkungen für den Codetyp, den Sie in eine Orchestratorfunktion schreiben können. Beispielsweise müssen Orchestratorfunktionen deterministisch sein: Eine Orchestratorfunktion wird mehrmals wiedergegeben, und sie muss jedes Mal dasselbe Ergebnis erzeugen.

Orchestratoren verwenden Event Sourcing, um eine zuverlässige Ausführung zu gewährleisten und den Zustand lokaler Variablen aufrechtzuerhalten. Das Wiedergabeverhalten von Orchestratorcode erstellt Einschränkungen für den Codetyp, den Sie in einem Orchestrator schreiben können. Beispielsweise müssen Orchestratoren deterministisch sein: Ein Orchestrator wird mehrmals ausgeführt, und er muss jedes Mal dasselbe Ergebnis produzieren.

Verwenden von deterministischen APIs

Im Folgenden sind einige einfache Richtlinien aufgeführt, um sicherzustellen, dass Ihr Code deterministisch ist.

Rufen Sie APIs aus Ihren Zielsprachen in Orchestratorfunktionen auf, verwenden Sie jedoch nur deterministische APIs. Eine deterministische API gibt immer denselben Wert für dieselbe Eingabe zurück, unabhängig davon, wann oder wie oft sie aufgerufen wird.

In den folgenden Abschnitten finden Sie Anleitungen zu APIs und Mustern, die Sie vermeiden sollten, da sie nicht deterministisch sind. Diese Einschränkungen gelten nur für Orchestratorfunktionen. Andere Funktionstypen haben keine solchen Einschränkungen.

Im Folgenden sind einige einfache Richtlinien aufgeführt, um sicherzustellen, dass Ihr Code deterministisch ist.

Rufen Sie APIs aus Ihren Zielsprachen in Orchestratoren auf, verwenden Sie jedoch nur deterministische APIs. Eine deterministische API gibt immer denselben Wert für dieselbe Eingabe zurück, unabhängig davon, wann oder wie oft sie aufgerufen wird.

In den folgenden Abschnitten finden Sie Anleitungen zu APIs und Mustern, die Sie vermeiden sollten, da sie nicht deterministisch sind. Diese Einschränkungen gelten nur für Orchestratoren. Aktivitäten haben keine solchen Einschränkungen.

Hinweis

In diesem Artikel werden allgemeine Orchestratorcodeeinschränkungen behandelt, aber sie ist nicht vollständig. Konzentrieren Sie sich darauf, ob eine API deterministisch ist. Mit dieser Denkweise können Sie in der Regel feststellen, welche APIs sicher zu verwenden sind, ohne auf diese Liste zu verweisen.

Datums- und Zeitangaben

Zeitbasierte APIs sind nicht deterministisch und sollten nie in Orchestratorfunktionen verwendet werden. Jede Orchestratorfunktionswiedergabe erzeugt einen anderen Wert. Verwenden Sie stattdessen die entsprechende API der Durable Functions zum Abrufen des aktuellen Datums oder der aktuellen Uhrzeit, während sie bei Wiedergaben konsistent bleibt.

Verwenden Sie keine DateTime.Now-, DateTime.UtcNow- oder vergleichbare APIs, um die aktuelle Uhrzeit abzurufen. Klassen wie Stopwatch sollten auch vermieden werden. Verwenden Sie für .NET In-Process Orchestrator-Funktionen die Eigenschaft IDurableOrchestrationContext.CurrentUtcDateTime, um die aktuelle Uhrzeit abzurufen. Verwenden Sie für .NET isolierten Orchestratorfunktionen die Eigenschaft TaskOrchestrationContext.CurrentDateTimeUtc, um die aktuelle Uhrzeit abzurufen.

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

Zeitbasierte APIs sind nicht deterministisch und sollten nie in Orchestratoren verwendet werden. Jede Orchestratorwiedergabe erzeugt einen anderen Wert. Verwenden Sie stattdessen die äquivalente API des Durable Task SDK zum Abrufen des aktuellen Datums oder der aktuellen Uhrzeit, die für alle Wiedergaben konsistent bleibt.

Verwenden Sie DateTime.Now, DateTime.UtcNow oder gleichwertige APIs nicht, um die aktuelle Uhrzeit abzurufen. Klassen wie Stopwatch sollten auch vermieden werden. Verwenden Sie die TaskOrchestrationContext.CurrentUtcDateTime Eigenschaft, um die aktuelle Uhrzeit abzurufen.

using Microsoft.DurableTask;

public class TimerExample : TaskOrchestrator<object?, TimeSpan>
{
    public override async Task<TimeSpan> RunAsync(TaskOrchestrationContext context, object? input)
    {
        // Use context.CurrentUtcDateTime instead of DateTime.Now or DateTime.UtcNow
        DateTime startTime = context.CurrentUtcDateTime;

        // do some work
        await context.CallActivityAsync("DoWork", null);

        TimeSpan totalTime = context.CurrentUtcDateTime.Subtract(startTime);
        return totalTime;
    }
}

GUIDs und UUIDs

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

Anstelle von APIs wie Guid.NewGuid(), verwenden Sie die API des Kontextobjekts NewGuid() , um eine zufällige GUID zu generieren, die für die Orchestratorwiedergabe sicher ist.

Guid randomGuid = context.NewGuid();

Hinweis

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

APIs, die eine zufällige GUID oder UUID zurückgeben, sind nicht deterministisch, da der generierte Wert für jede Wiedergabe unterschiedlich ist. Je nach Sprache ist möglicherweise eine integrierte API zum Generieren deterministischer GUIDs oder UUIDs verfügbar. Verwenden Sie andernfalls eine Aktivität, um eine zufällig generierte GUID oder UUID zurückzugeben.

Anstelle von APIs wie Guid.NewGuid(), verwenden Sie die API des Kontextobjekts NewGuid() , um eine zufällige GUID zu generieren, die für die Orchestratorwiedergabe sicher ist.

using Microsoft.DurableTask;

public class GuidExample : TaskOrchestrator<object?, Guid>
{
    public override async Task<Guid> RunAsync(TaskOrchestrationContext context, object? input)
    {
        // Use context.NewGuid() instead of Guid.NewGuid()
        Guid randomGuid = context.NewGuid();
        return randomGuid;
    }
}

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 sind.

Alternativ können Sie einen Zufallszahlengenerator mit einem festen Startwert direkt in einer Orchestratorfunktion verwenden. Dieser Ansatz ist sicher, solange für jede Orchestrierungswiedergabe dieselbe Sequenz von Zahlen generiert wird.

Verwenden Sie eine Aktivität, um Zufallszahlen an einen Orchestrator zurückzugeben. Die Rückgabewerte von Aktivitäten sind immer sicher für die Wiedergabe, da sie in der Orchestrierungshistorie gespeichert sind.

Alternativ können Sie einen Zufallszahlengenerator mit einem festen Seedwert direkt in einem Orchestrator verwenden. Dieser Ansatz ist sicher, solange für jede Orchestrierungswiedergabe dieselbe Sequenz von Zahlen generiert wird.

Bindungen

Verwenden Sie keine Bindungen in einer Orchestratorfunktion, einschließlich der Bindungen des Orchestrierungsclients und des Entitätsclients. Verwenden Sie Eingabe- und Ausgabebindungen nur in einer Client- oder Aktivitätsfunktion. Orchestratorfunktionen können mehrmals wiedergegeben werden, was zu nicht deterministischen und doppelten E/A-Vorgängen mit externen Systemen führt.

Orchestratoren sollten keine direkten E/A-Vorgänge mit externen Systemen ausführen. Verschieben Sie die E/A-Vorgänge in Aktivitäten. Orchestratoren können mehrfach ausgeführt werden, was zu nicht deterministischen und doppelten E/A-Vorgängen mit externen Systemen führt.

Statische Variablen

Statische Variablen können sich im Laufe der Zeit ändern, wodurch sie für Orchestratorfunktionen unsicher sind. Vermeiden Sie die Verwendung statischer Variablen in Orchestratorfunktionen, da sich ihre 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 statischer Variablen auf Aktivitätsfunktionen.

Statische Variablen können sich im Laufe der Zeit ändern, wodurch sie für Orchestratoren unsicher sind. Vermeiden Sie die Verwendung statischer Variablen in Orchestratoren, da sich ihre 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 statischer Variablen auf Aktivitäten.

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. Vermeiden Sie statische Variablen, außer in bestimmten Anwendungsfällen, wie z. B. Best-Effort-In-Memory-Caching in Funktionen von Aktivitäten oder Entitäten.

Umgebungsvariablen

Umgebungsvariablen in Orchestratorfunktionen 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.

Umgebungsvariablen in Orchestratoren können sich im Laufe der Zeit ändern, was zu einem nicht deterministischen Laufzeitverhalten führt. Wenn ein Orchestrator eine konfiguration benötigt, die in einer Umgebungsvariable definiert ist, müssen Sie den Konfigurationswert als Eingabe oder als Rückgabewert einer Aktivität an den Orchestrator übergeben.

Netzwerk und HTTP

Verwenden Sie Aktivitätsfunktionen, um ausgehende Netzwerkaufrufe auszuführen. Wenn Sie einen HTTP-Aufruf von Ihrer Orchestratorfunktion ausführen müssen, können Sie auch die dauerhaften HTTP-APIs verwenden.

Verwenden Sie Aktivitäten, um ausgehende Netzwerkanrufe durchzuführen. Orchestratoren sollten niemals direkte HTTP-Aufrufe oder andere Netzwerkanforderungen durchführen, da diese Vorgänge nicht deterministisch sind.

Threadblockierungs-APIs

Das Blockieren von APIs wie sleep kann zu Leistungs- und Skalierungsproblemen für Orchestratorfunktionen führen und zu unnötigen Ausführungszeiten im Azure Functions Verbrauchsplan führen. Verwenden Sie Alternativen, wenn sie 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 des Orchestrators zählen.

Das Blockieren von APIs wie "Standby" kann zu Leistungs- und Skalierungsproblemen für Orchestratoren führen und sollten vermieden werden. Verwenden Sie dauerhafte Zeitgeber, um Verzögerungen zu erstellen, die für die Wiedergabe sicher sind.

Verwenden Sie context.CreateTimer() anstelle von Task.Delay() oder Thread.Sleep().

// Don't use Task.Delay() or Thread.Sleep()
// Use context.CreateTimer() instead
await context.CreateTimer(context.CurrentUtcDateTime.AddMinutes(5), CancellationToken.None);

Asynchrone Programmierschnittstellen (APIs)

Orchestrator-Code darf niemals einen asynchronen Vorgang starten, außer Vorgänge, die vom Kontextobjekt des Orchestrierungstriggers definiert sind. Verwenden Sie beispielsweise nie Task.Run, Task.Delay und HttpClient.SendAsync in .NET oder setTimeout und setInterval in JavaScript. Eine Orchestratorfunktion sollte nur asynchrone Arbeiten mithilfe von Durable SDK-APIs planen, wie das Planen von Aktivitätsfunktionen. Alle anderen asynchronen Aufrufe sollten innerhalb von Aktivitätsfunktionen ausgeführt werden.

Orchestrator-Code darf niemals einen asynchronen Vorgang starten, außer Vorgänge, die vom Orchestrierungskontextobjekt definiert sind. Verwenden Sie beispielsweise nie Task.Run, Task.Delay und HttpClient.SendAsync in .NET. Ein Orchestrator sollte nur asynchrone Arbeit mithilfe von Durable Task SDK-APIs planen, z. B. Terminplanungsaktivitäten. Alle anderen asynchronen Aufrufe sollten innerhalb von Aktivitäten ausgeführt werden.

Asynchrone JavaScript-Funktionen

Deklarieren Sie JavaScript-Orchestratorfunktionen als synchrone Generatorfunktionen. Deklarieren Sie JavaScript-Orchestratorfunktionen nicht als async, da die Node.js-Laufzeit kein deterministisches Verhalten für async Funktionen garantiert.

Python Coroutinen

Deklarieren Sie nicht Python Orchestratorfunktionen als Coroutines. Verwenden Sie das Schlüsselwort async nicht, da die Semantik der Coroutines nicht mit dem Wiedergabemodell von Durable Functions kompatibel ist. Deklarieren Sie Python Orchestratorfunktionen als Generatoren, und verwenden Sie yield anstelle von await mit der context-API.

Sie dürfen Python Orchestratoren nicht als Coroutinen deklarieren. Anders ausgedrückt: Deklarieren Sie niemals Python-Orchestratoren mit dem Schlüsselwort async, da die coroutine-Semantik nicht mit dem Replay-Modell des Durable Task übereinstimmt. Sie müssen Python Orchestratoren immer als Generatoren deklarieren, was bedeutet, dass Sie yield anstelle von await beim Aufrufen von Kontext-APIs verwenden sollten.

from durabletask import task

# CORRECT - use yield (generator function)
def my_orchestrator(ctx: task.OrchestrationContext, input: str):
    result = yield ctx.call_activity(my_activity, input=input)
    return result

# WRONG - don't use async/await
async def bad_orchestrator(ctx: task.OrchestrationContext, input: str):
    result = await ctx.call_activity(my_activity, input=input)  # This won't work!
    return result

.NET Threading-APIs

Das Durable Task Framework führt Orchestratorcode in einem einzigen Thread aus und kann nicht mit anderen Threads interagieren. Das Ausführen asynchroner Fortsetzungen auf einem Thread im Workerpool während der Ausführung einer Orchestrierung kann zu einer nichtdeterministischen Ausführung oder zu Deadlocks führen. Aus diesem Grund sollten Ihre Orchestratorfunktionen fast nie Threading-APIs verwenden. Verwenden Sie ConfigureAwait(continueOnCapturedContext: false) beispielsweise niemals in einer Orchestratorfunktion, um sicherzustellen, dass Aufgabenfortsetzungen auf dem ursprünglichen SynchronizationContext der Orchestratorfunktion ausgeführt werden.

Hinweis

Das Durable Task Framework ist darauf ausgelegt, die versehentliche Verwendung von Nichtorchestratorthreads in Orchestratorfunktionen zu erkennen. Wenn ein Verstoß gefunden wird, löst das Framework eine NonDeterministicOrchestrationException-Ausnahme aus. Dieses Erkennungsverhalten fängt jedoch nicht alle Verletzungen auf, und Sie sollten nicht davon abhängen.

Das Durable Task Framework führt Orchestratorcode in einem einzigen Thread aus und kann nicht mit anderen Threads interagieren. Das Ausführen asynchroner Fortsetzungen auf einem Thread im Workerpool während der Ausführung einer Orchestrierung kann zu einer nichtdeterministischen Ausführung oder zu Deadlocks führen. Aus diesem Grund sollten Ihre Orchestrator fast nie Threading-APIs verwenden. Verwenden Sie beispielsweise niemals ConfigureAwait(continueOnCapturedContext: false) in einem Orchestrator, um sicherzustellen, dass Aufgabenfortsetzungen auf dem ursprünglichen OrchestratorSynchronizationContext ausgeführt werden.

Hinweis

Das Durable Task Framework versucht, die versehentliche Verwendung von Nicht-Orchestrator-Threads in Orchestrierungen zu erkennen. Wenn ein Verstoß gefunden wird, löst das Framework eine NonDeterministicOrchestrationException-Ausnahme aus. Dieses Erkennungsverhalten fängt jedoch nicht alle Verletzungen auf, und Sie sollten nicht davon abhängen.

Versionsverwaltung

Eine dauerhafte Orchestrierung kann für Tage, Monate, Jahre oder sogar als ewige Orchestrierung laufen. Codeänderungen, die sich auf die Ausführung von Orchestrierungen auswirken, können das Wiedergabeverhalten unterbrechen. Planen Sie daher sorgfältig, bevor Sie Ihre App aktualisieren. Weitere Informationen finden Sie unter Versionsverwaltung.

Eine dauerhafte Orchestrierung kann für Tage, Monate, Jahre oder sogar unbegrenzt ausgeführt werden. Codeänderungen, die sich auf die Ausführung von Orchestrierungen auswirken, können das Wiedergabeverhalten unterbrechen. Planen Sie daher sorgfältig, bevor Sie Ihre App aktualisieren. Allgemeine Versionsverwaltungsstrategien umfassen die parallele Bereitstellung und die Verwendung versionsspezifischer Aufgabenhubnamen.

Dauerhafte Aufgaben

Hinweis

In diesem Abschnitt werden die internen Implementierungsdetails des Durable Task Framework beschrieben. Sie müssen diese Informationen nicht kennen, um Durable Functions zu verwenden, aber es hilft, das Wiedergabeverhalten zu erläutern.

Aufgaben, die in Orchestratorfunktionen sicher warten können, werden manchmal als dauerhafte Aufgaben bezeichnet. Das dauerhafte Aufgabenframework erstellt und verwaltet diese Aufgaben. Beispiele sind die von CallActivityAsync, WaitForExternalEvent und CreateTimer in .NET Orchestratorfunktionen zurückgegebenen Aufgaben.

Eine Liste der TaskCompletionSource-Objekte in .NET verwaltet diese dauerhaften Aufgaben intern. Während der Wiedergabe erstellt Orchestratorcode diese Aufgaben. Der Dispatcher führt sie zu Ende, während die entsprechenden Verlaufsereignisse aufgelistet werden.

Die Laufzeitumgebung führt die Aufgaben synchron in einem einzelnen Thread aus, bis der Verlauf wiedergegeben wird. Wenn eine dauerhafte Aufgabe nicht bis zum Ende der Verlaufswiedergabe abgeschlossen ist, führt das Laufzeitsystem die entsprechenden Aktionen aus. Beispielsweise kann die Laufzeitumgebung eine Nachricht einreihen, um eine Aktivitätsfunktion aufzurufen.

In diesem Laufzeitverhalten wird erläutert, warum Ihre Orchestratorfunktion await oder yield in einer „nicht-dauerhaften“ Aufgabe nicht verwenden kann. Der Dispatcherthread kann nicht darauf warten, dass die Aufgabe abgeschlossen wird, und Rückrufe aus dieser Aufgabe können den Überwachungsstatus der Orchestratorfunktion beeinträchtigen. Die Laufzeit enthält Überprüfungen, um diese Verletzungen zu erkennen.

Weitere Informationen dazu, wie das Durable Task Framework Orchestratorfunktionen ausführt, finden Sie im Quellcode Durable Task unter GitHub. Insbesondere siehe TaskOrchestrationExecutor.cs und TaskOrchestrationContext.cs.

Aufgaben, die in Orchestratoren sicher warten können, werden manchmal als dauerhafte Aufgaben bezeichnet. Das dauerhafte Aufgabenframework erstellt und verwaltet diese Aufgaben. Beispiele hierfür sind die von CallActivityAsync, WaitForExternalEvent und CreateTimer in .NET Orchestratoren zurückgegebenen Aufgaben.

Eine Liste der TaskCompletionSource-Objekte in .NET verwaltet diese dauerhaften Aufgaben intern. Während der Wiedergabe erstellt Orchestratorcode diese Aufgaben. Der Dispatcher führt sie zu Ende, während die entsprechenden Verlaufsereignisse aufgelistet werden.

Die Laufzeitumgebung führt die Aufgaben synchron in einem einzelnen Thread aus, bis der Verlauf wiedergegeben wird. Wenn eine dauerhafte Aufgabe nicht bis zum Ende der Verlaufswiedergabe abgeschlossen ist, führt das Laufzeitsystem die entsprechenden Aktionen aus. Beispielsweise kann die Laufzeit eine Nachricht auffordern, um eine Aktivität aufzurufen.

Dieses Laufzeitverhalten erklärt, warum Ihr Orchestrator await oder yield nicht in einer nicht dauerhaften Aufgabe verwenden kann. Der Dispatcherthread kann nicht warten, bis die Aufgabe abgeschlossen ist, und Rückrufe aus dieser Aufgabe können den Nachverfolgungsstatus des Orchestrators beschädigen. Die Laufzeit enthält Überprüfungen, um diese Verletzungen zu erkennen.

Weitere Informationen dazu, wie das Durable Task Framework Orchestrators ausführt, finden Sie im Quellcode Durable Task unter GitHub. Insbesondere siehe TaskOrchestrationExecutor.cs und TaskOrchestrationContext.cs.

Nächste Schritte