Freigeben über


Fehlerbehandlung in Durable Functions (Azure Functions)

Durable Function-Orchestrierungen sind im Code implementiert und können die integrierten Funktionen zur Fehlerbehandlung der Programmiersprache nutzen. Daher gibt es eigentlich keine neuen Konzepte, die Sie bei der Einbindung der Fehlerbehandlung und Kompensierung in Ihre Orchestrierungen beachten müssen. Es gibt jedoch einige wenige Verhaltensweisen, die Sie kennen sollten:

Hinweis

Version 4 des Node.js-Programmiermodells für Azure Functions ist allgemein verfügbar. Das neue Modell V4 ist für ein flexibleres und intuitiveres Benutzererlebnis für JavaScript- und TypeScript-Entwickler konzipiert. Erfahren Sie mehr über die Unterschiede zwischen v3 und v4 in der Migrationsanleitung.

In den folgenden Codeschnipseln steht JavaScript (PM4) für das Programmiermodell V4, das neue Benutzererlebnis.

Fehler in Aktivitätsfunktionen und Suborchestrierungen

In dauerhaften Funktionen werden unbehandelte Ausnahmen, die innerhalb von Aktivitätsfunktionen oder Unterorchestrierungen ausgelöst werden, mithilfe standardisierter Ausnahmetypen zurück zur Orchestratorfunktion gemarshallt.

Betrachten Sie beispielsweise die folgende Orchestratorfunktion, die eine Überweisung zwischen zwei Konten durchführt:

In Durable Functions C# in-process werden unbehandelte Ausnahmen als FunctionFailedException ausgelöst.

Die Ausnahmemeldung identifiziert in der Regel, welche Aktivitätsfunktionen oder Sub-Orchestrierungen den Fehler verursacht haben. Um auf detailliertere Fehlerinformationen zuzugreifen, überprüfen Sie die InnerException Eigenschaft.

[FunctionName("TransferFunds")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var transferDetails = context.GetInput<TransferOperation>();

    await context.CallActivityAsync("DebitAccount",
        new
        {
            Account = transferDetails.SourceAccount,
            Amount = transferDetails.Amount
        });

    try
    {
        await context.CallActivityAsync("CreditAccount",
            new
            {
                Account = transferDetails.DestinationAccount,
                Amount = transferDetails.Amount
            });
    }
    catch (FunctionFailedException)
    {
        // Refund the source account.
        // Another try/catch could be used here based on the needs of the application.
        await context.CallActivityAsync("CreditAccount",
            new
            {
                Account = transferDetails.SourceAccount,
                Amount = transferDetails.Amount
            });
    }
}

Hinweis

Die vorherigen C#-Beispiele gelten für Durable Functions 2.x. Für Durable Functions 1.x müssen Sie DurableOrchestrationContext anstelle von IDurableOrchestrationContext verwenden. Weitere Informationen zu den Unterschieden zwischen den Versionen finden Sie im Artikel Durable Functions-Versionen.

Wenn der erste Funktionsaufruf von CreditAccount fehlschlägt, wird dies durch die Orchestratorfunktion kompensiert, indem die Gelder auf das Quellkonto zurücküberwiesen werden.

Fehler in Entitätsfunktionen

Das Verhalten der Ausnahmebehandlung für Entitätsfunktionen unterscheidet sich je nach dem Hostingmodell für dauerhafte Funktionen:

Bei dauerhaften Funktionen, die C#-In-Process verwenden, werden ursprüngliche Ausnahmetypen, die von Entitätsfunktionen ausgelöst werden, direkt an den Orchestrator zurückgegeben.

[FunctionName("Function1")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    try
    {
        var entityId = new EntityId(nameof(Counter), "myCounter");
        await context.CallEntityAsync(entityId, "Add", 1);
    }
    catch (Exception ex)
    {
        // The exception type will be InvalidOperationException with the message "this is an entity exception".
    }
    return string.Empty;
}

[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            throw new InvalidOperationException("this is an entity exception");
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
    }
}

Automatische Wiederholung bei einem Fehler

Wenn Sie Aktivitätsfunktionen oder untergeordnete Orchestrierungsfunktionen aufrufen, können Sie eine Richtlinie für automatische Wiederholungen angeben. Im folgenden Beispiel wird versucht, eine Funktion bis zu 3-mal mit je 5 Sekunden Wartezeit zwischen den einzelnen Wiederholungsversuchen aufzurufen:

[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var retryOptions = new RetryOptions(
        firstRetryInterval: TimeSpan.FromSeconds(5),
        maxNumberOfAttempts: 3);

    await context.CallActivityWithRetryAsync("FlakyFunction", retryOptions, null);

    // ...
}

Hinweis

Die vorherigen C#-Beispiele gelten für Durable Functions 2.x. Für Durable Functions 1.x müssen Sie DurableOrchestrationContext anstelle von IDurableOrchestrationContext verwenden. Weitere Informationen zu den Unterschieden zwischen den Versionen finden Sie im Artikel Durable Functions-Versionen.

Der Aktivitätsfunktionsaufruf im vorherigen Beispiel nimmt einen Parameter zum Konfigurieren einer automatischen Wiederholungsrichtlinie. Es stehen mehrere Optionen zur Verfügung, um die automatische Wiederholungsrichtlinie anzupassen:

  • Maximale Anzahl von Versuchen: Die maximale Anzahl von Versuchen. Wenn 1 festgelegt ist, wird kein Wiederholungsversuch durchgeführt.
  • First retry interval (Erstes Wiederholungsintervall): Die Zeitspanne bis zum Ablauf des ersten Wiederholungsversuchs.
  • Backoff-Koeffizient: Der Koeffizient, der verwendet wird, um den Anstieg der Backoff-Intervalle zu bestimmen. Der Standardwert lautet 1.
  • Max retry interval (Maximales Wiederholungsintervall): die maximale Zeitspanne zwischen den Wiederholungsversuchen.
  • Retry timeout (Timeout wiederholen): die maximale Zeitspanne für das Ausführen von Wiederholungsversuchen. Das Standardverhalten ist das Wiederholen auf unbestimmte Zeit.

Benutzerdefinierte Wiederholungshandler

Wenn Sie .NET oder Java verwenden, haben Sie auch die Möglichkeit, Wiederholungshandler im Code zu implementieren. Dies ist nützlich, wenn deklarative Wiederholungsrichtlinien nicht ausdrucksstark genug sind. Bei Sprachen, die keine benutzerdefinierten Wiederholungshandler unterstützen, haben Sie weiterhin die Möglichkeit, Wiederholungsrichtlinien mithilfe von Schleifen, Ausnahmebehandlung und Timern zum Einfügen von Verzögerungen zwischen Wiederholungsversuchen zu implementieren.

RetryOptions retryOptions = new RetryOptions(
    firstRetryInterval: TimeSpan.FromSeconds(5),
    maxNumberOfAttempts: int.MaxValue)
    {
        Handle = exception =>
        {
            // True to handle and try again, false to not handle and throw.
            if (exception is TaskFailedException failure)
            {
                // Exceptions from TaskActivities are always this type. Inspect the
                // inner Exception to get more details.
            }

            return false;
        };
    }

await ctx.CallActivityWithRetryAsync("FlakeyActivity", retryOptions, null);

Funktion-Timeouts

Vielleicht möchten Sie einen Funktionsaufruf innerhalb einer Orchestratorfunktion verwerfen, wenn der Vorgang zu lange dauert. Die richtige Vorgehensweise dafür ist das Erstellen eines permanenten Timers mit einem any-Taskselektor, wie im folgenden Beispiel gezeigt:

[FunctionName("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    TimeSpan timeout = TimeSpan.FromSeconds(30);
    DateTime deadline = context.CurrentUtcDateTime.Add(timeout);

    using (var cts = new CancellationTokenSource())
    {
        Task activityTask = context.CallActivityAsync("FlakyFunction");
        Task timeoutTask = context.CreateTimer(deadline, cts.Token);

        Task winner = await Task.WhenAny(activityTask, timeoutTask);
        if (winner == activityTask)
        {
            // success case
            cts.Cancel();
            return true;
        }
        else
        {
            // timeout case
            return false;
        }
    }
}

Hinweis

Die vorherigen C#-Beispiele gelten für Durable Functions 2.x. Für Durable Functions 1.x müssen Sie DurableOrchestrationContext anstelle von IDurableOrchestrationContext verwenden. Weitere Informationen zu den Unterschieden zwischen den Versionen finden Sie im Artikel Durable Functions-Versionen.

Hinweis

Dieser Mechanismus beendet die Ausführung der laufenden Aktivitätsfunktion nicht tatsächlich. Stattdessen lässt er zu, dass die Orchestratorfunktion das Ergebnis ignoriert und fortfährt. Weitere Informationen finden Sie in der Dokumentation zu Timern.

Nicht behandelte Ausnahmen

Fällt eine Orchestratorfunktion mit einer nicht behandelten Ausnahme aus, werden die Details der Ausnahme protokolliert, und die Instanz wird mit einem Failed-Status abgeschlossen.

Benutzerdefinierte Ausnahmeeigenschaften für FailureDetails (.NET Isolated) einschließen

Wenn Sie dauerhafte Aufgabenworkflows im isolierten .NET-Modell ausführen, werden Vorgangsfehler automatisch in ein FailureDetails-Objekt serialisiert. Standardmäßig enthält dieses Objekt Standardfelder wie:

  • ErrorType – der Name des Ausnahmetyps
  • Nachricht – Die Ausnahmemeldung
  • StackTrace – die serialisierte Stapelablaufverfolgung
  • InnerFailure – ein geschachteltes FailureDetails-Objekt für rekursive innere Ausnahmen

Ab Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 können Sie dieses Verhalten erweitern, indem Sie einen IExceptionPropertiesProvider implementieren (definiert im Microsoft.DurableTask.Worker ab v1.16.1-Paket). Dieser Anbieter definiert, welche Ausnahmetypen und welche ihrer Eigenschaften im Wörterbuch FailureDetails.Properties enthalten sein sollen.

Hinweis

  • Dieses Feature ist nur in .NET Isolated verfügbar. Unterstützung für Java wird in einer zukünftigen Version hinzugefügt.
  • Stellen Sie sicher, dass Sie Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 oder höher verwenden.
  • Stellen Sie sicher, dass Sie Microsoft.DurableTask.Worker v1.16.1 oder höher verwenden.

Implementieren eines Ausnahmeeigenschaften-Providers

Implementieren Sie einen benutzerdefinierten IExceptionPropertiesProvider, um ausgewählte Eigenschaften für die gewünschten Ausnahmen zu extrahieren und zurückzugeben. Das zurückgegebene Wörterbuch wird beim Auslösen eines übereinstimmenden Ausnahmetyps in das Feld „Eigenschaften“ von FailureDetails serialisiert.

using Microsoft.DurableTask.Worker;

public class CustomExceptionPropertiesProvider : IExceptionPropertiesProvider
{
    public IDictionary<string, object?>? GetExceptionProperties(Exception exception)
    {
        return exception switch
        {
            ArgumentOutOfRangeException e => new Dictionary<string, object?>
            {
                ["ParamName"] = e.ParamName,
                ["ActualValue"] = e.ActualValue
            },
            InvalidOperationException e => new Dictionary<string, object?>
            {
                ["CustomHint"] = "Invalid operation occurred",
                ["TimestampUtc"] = DateTime.UtcNow
            },
            _ => null // Other exception types not handled
        };
    }
}

Registrieren des Anbieters

Registrieren Sie Ihren benutzerdefinierten IExceptionPropertiesProvider in Ihrem .NET Isolated Worker-Host, in der Regel in Program.cs:

using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder =>
    {
        // Register custom exception properties provider
        builder.Services.AddSingleton<IExceptionPropertiesProvider, CustomExceptionPropertiesProvider>();
    })
    .Build();

host.Run();

Nach der Registrierung enthalten alle Ausnahmen, die mit einem der behandelten Typen übereinstimmen, automatisch die konfigurierten Eigenschaften in den FailureDetails.

Beispiel für eine FailureDetails-Ausgabe

Wenn eine Ausnahme auftritt, die der Konfiguration Ihres Anbieters entspricht, empfängt die Orchestrierung eine serialisierte FailureDetails-Struktur wie folgt:

{
  "errorType": "TaskFailedException",
  "message": "Activity failed with an exception.",
  "stackTrace": "...",
  "innerFailure": {
    "errorType": "ArgumentOutOfRangeException",
    "message": "Specified argument was out of range.",
    "properties": {
      "ParamName": "count",
      "ActualValue": 42
    }
  }
}

Nächste Schritte