Modellieren des Abbruchverhaltens in Workflows

Aktivitäten können innerhalb eines Workflows abgebrochen werden, z. B. durch eine Parallel-Aktivität, die unvollständige Verzweigungen abbricht, wenn ihre CompletionConditiontrue ergibt, oder von außerhalb des Workflows, wenn der Host Cancel aufruft. Workflowautoren können die Abbruchbehandlung mithilfe der CancellationScope-Aktivität oder der CompensableActivity-Aktivität bereitstellen oder können benutzerdefinierte Aktivitäten erstellen, die eine Abbruchlogik bereitstellen. Dieses Thema bietet eine Übersicht über den Abbruch in Workflows.

Abbruch, Kompensation und Transaktionen

Wenn Sie Transaktionen verwenden, kann Ihre Anwendung alle Änderungen zurücknehmen (Rollback), die innerhalb der Transaktion ausgeführt wurden, falls während des Transaktionsprozesses ein Fehler auftritt. Möglicherweise ist jedoch nicht die gesamte Arbeit, die abgebrochen oder rückgängig gemacht werden muss, für Transaktionen geeignet, beispielsweise Prozesse mit langer Laufzeit oder Aufgaben, die keine Transaktionsressourcen einschließen. Die Kompensation stellt ein Modell zum Rückgängigmachen von zuvor abgeschlossenen, nicht transaktionalen Aufgaben bereit, wenn im Workflow ein nachfolgender Fehler auftritt. Der Abbruch stellt ein Modell für Workflow- und Aktivitätsautoren zum Behandeln von nicht transaktionalen Aufgaben bereit, die noch nicht abgeschlossen waren. Wenn die Ausführung einer Aktivität nicht abgeschlossen war und abgebrochen wird, wird die Abbruchlogik aufgerufen, falls verfügbar.

Hinweis

Weitere Informationen zu Transaktionen und Entschädigungen finden Sie unter Transaktionen und Entschädigungen.

Verwenden von CancellationScope

Die CancellationScope-Aktivität verfügt über zwei Abschnitte, die untergeordnete Aktivitäten enthalten können: Body und CancellationHandler. Im Body befinden sich die Aktivitäten, aus denen die Logik der Aktivität besteht, und im CancellationHandler befinden sich die Aktivitäten, die die Abbruchlogik für die Aktivität bereitstellen. Eine Aktivität kann nur abgebrochen werden, wenn sie noch nicht abgeschlossen wurde. Im Fall der CancellationScope-Aktivität verweist der Abschluss auf den Abschluss der Aktivitäten im Body. Wenn eine Abbruchanforderung geplant ist und die Aktivitäten im Body nicht abgeschlossen wurden, dann wird der CancellationScope als Canceled markiert, und die CancellationHandler-Aktivitäten werden ausgeführt.

Abbrechen eines Workflows vom Host aus

Ein Host kann einen Workflow abbrechen, indem er die Cancel-Methode der WorkflowApplication-Instanz aufruft, die den Workflow hostet. Im folgenden Beispiel wird ein Workflow erstellt, der einen CancellationScope aufweist. Der Workflow wird aufgerufen und anschließend ruft der Host Cancel auf. Die Hauptausführung des Workflows wird beendet, der CancellationHandler des CancellationScope wird aufgerufen, und dann wird der Workflow mit einem Status von Canceled abgeschlossen.

Activity wf = new CancellationScope
{
    Body = new Sequence
    {
        Activities =
        {
            new WriteLine
            {
                Text = "Starting the workflow."
            },
            new Delay
            {
                Duration = TimeSpan.FromSeconds(5)
            },
            new WriteLine
            {
                Text = "Ending the workflow."
            }
        }
    },
    CancellationHandler = new WriteLine
    {
        Text = "CancellationHandler invoked."
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Subscribe to any desired workflow lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

Thread.Sleep(TimeSpan.FromSeconds(1));

wfApp.Cancel();

Wenn dieser Workflow aufgerufen wird, wird die folgende Ausgabe in der Konsole angezeigt.

Starten des Workflows.
CancellationHandler aufgerufen.Workflow b30ebb30-df46-4d90-a211-e31c38d8db3c Abgebrochen.

Hinweis

Wenn eine CancellationScope-Aktivität abgebrochen und der CancellationHandler aufgerufen wird, liegt es in der Verantwortung des Workflowautors, den Status der abgebrochenen Aktivität vor dem Abbruch zu bestimmen, um die entsprechende Abbruchlogik bereitzustellen. Der CancellationHandler stellt keine Informationen zum Status der abgebrochenen Aktivität bereit.

Ein Workflow kann auch vom Host abgebrochen werden, wenn eine nicht behandelte Ausnahme nach dem Stamm des Workflows ausgelöst wird und der OnUnhandledException-Handler Cancel zurückgibt. In diesem Beispiel startet der Workflow und löst dann eine ApplicationException aus. Diese Ausnahme wird vom Workflow nicht behandelt, daher wird der OnUnhandledException-Handler aufgerufen. Der Handler weist die Laufzeit an, den Workflow abzubrechen, und der CancellationHandler der derzeit ausgeführten CancellationScope-Aktivität wird aufgerufen.

Activity wf = new CancellationScope
{
    Body = new Sequence
    {
        Activities =
        {
            new WriteLine
            {
                Text = "Starting the workflow."
            },
            new Throw
            {
                 Exception = new InArgument<Exception>((env) =>
                     new ApplicationException("An ApplicationException was thrown."))
            },
            new WriteLine
            {
                Text = "Ending the workflow."
            }
        }
    },
    CancellationHandler = new WriteLine
    {
        Text = "CancellationHandler invoked."
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Subscribe to any desired workflow lifecycle events.
wfApp.OnUnhandledException = delegate (WorkflowApplicationUnhandledExceptionEventArgs e)
{
    // Display the unhandled exception.
    Console.WriteLine("OnUnhandledException in Workflow {0}\n{1}",
        e.InstanceId, e.UnhandledException.Message);

    // Instruct the runtime to cancel the workflow.
    return UnhandledExceptionAction.Cancel;
};

wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

Wenn dieser Workflow aufgerufen wird, wird die folgende Ausgabe in der Konsole angezeigt.

Starten des Workflows.
OnUnhandledException in Workflow 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9An ApplicationException was thrown.CancellationHandler invoked.Workflow 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 Canceled.

Abbrechen einer Aktivität innerhalb eines Workflows

Eine Aktivität kann auch von ihrem übergeordneten Element abgebrochen werden. Wenn eine Parallel-Aktivität beispielsweise über mehrere ausgeführte Branches verfügt, und ihre CompletionCondition ergibt true, werden ihre unvollständigen Branches abgebrochen. In diesem Beispiel wird eine Parallel-Aktivität erstellt, das zwei Branches aufweist. Ihre CompletionCondition ist auf true festgelegt, damit die Parallel abgeschlossen wird, sobald der einzelne Branch abgeschlossen sind. In diesem Beispiel wird Branch 2 abgeschlossen, daher wird Branch 1 abgebrochen.

Activity wf = new Parallel
{
    CompletionCondition = true,
    Branches =
    {
        new CancellationScope
        {
            Body = new Sequence
            {
                Activities =
                {
                    new WriteLine
                    {
                        Text = "Branch 1 starting."
                    },
                    new Delay
                    {
                         Duration = TimeSpan.FromSeconds(2)
                    },
                    new WriteLine
                    {
                        Text = "Branch 1 complete."
                    }
                }
            },
            CancellationHandler = new WriteLine
            {
                Text = "Branch 1 canceled."
            }
        },
        new WriteLine
        {
            Text = "Branch 2 complete."
        }
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

Wenn dieser Workflow aufgerufen wird, wird die folgende Ausgabe in der Konsole angezeigt.

Starten von Branch 1.
Branch 2 abgeschlossen.Branch 1 abgebrochen.Workflow e0685e24-18ef-4a47-acf3-5c638732f3be abgeschlossen. Aktivitäten werden auch abgebrochen, wenn eine Ausnahme an der Wurzel der Aktivität vorbei auftaucht, aber auf einer höheren Ebene im Workflow behandelt wird. In diesem Beispiel besteht die Hauptlogik des Workflows aus einer Sequence-Aktivität. Die Sequence wird als Body einer CancellationScope-Aktivität angegeben, die in einer TryCatch-Aktivität enthalten ist. Aus dem Text der Sequence wird eine Ausnahme ausgelöst, die von der übergeordneten TryCatch-Aktivität behandelt wird, und die Sequence wird abgebrochen.

Activity wf = new TryCatch
{
    Try = new CancellationScope
    {
        Body = new Sequence
        {
            Activities =
            {
                new WriteLine
                {
                    Text = "Sequence starting."
                },
                new Throw
                {
                     Exception = new InArgument<Exception>((env) =>
                         new ApplicationException("An ApplicationException was thrown."))
                },
                new WriteLine
                {
                    Text = "Sequence complete."
                }
            }
        },
        CancellationHandler = new WriteLine
        {
            Text = "Sequence canceled."
        }
    },
    Catches =
    {
        new Catch<ApplicationException>
        {
            Action = new ActivityAction<ApplicationException>
            {
                Handler  = new WriteLine
                {
                    Text = "Exception caught."
                }
            }
        }
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

Wenn dieser Workflow aufgerufen wird, wird die folgende Ausgabe in der Konsole angezeigt.

Starten der Sequenz.
Sequenz abgebrochen.Ausnahme abgefangen.Workflow e3c18939-121e-4c43-af1c-ba1ce977ce55 Abgeschlossen.

Auslösen von Ausnahmen von einem CancellationHandler aus

Alle vom CancellationHandler eines CancellationScope ausgelösten Ausnahmen haben schwerwiegende Auswirkungen auf den Workflow. Wenn für Ausnahmen die Möglichkeit besteht, einen CancellationHandler zu umgehen, verwenden Sie im TryCatchCancellationHandler, um diese Ausnahmen abzufangen und zu behandeln.

Abbruch mithilfe von CompensableActivity

Genau wie die CancellationScope-Aktivität verfügt die CompensableActivity über einen CancellationHandler. Wenn eine CompensableActivity abgebrochen wird, werden alle Aktivitäten in ihrem CancellationHandler aufgerufen. Dies kann nützlich für das Rückgängigmachen teilweise abgeschlossener Arbeit sein. Weitere Informationen zur Verwendung von für die Kompensation und den Abbruch finden Sie unter CompensableActivityKompensierung.

Abbruch mithilfe von benutzerdefinierten Aktivitäten

Für Autoren von benutzerdefinierten Aktivitäten gibt es mehrere Möglichkeiten, eine Abbruchlogik in ihre benutzerdefinierten Aktivitäten zu implementieren. Benutzerdefinierte Aktivitäten, die von Activity abgeleitet werden, können Abbruchlogik implementieren, indem CancellationScope oder eine andere benutzerdefinierte Aktivität, die Abbruchlogik enthält, in den Text der Aktivität eingefügt wird. Von AsyncCodeActivity und NativeActivity abgeleitete Aktivitäten können ihre jeweilige Cancel-Methode überschreiben und an dieser Stelle eine Abbruchlogik bereitstellen. Von CodeActivity abgeleitete Aktivitäten stellen keine Möglichkeit zum Abbruch dar, da die gesamte Arbeit in einer einzigen Ausführung ausgeführt wird, wenn die Laufzeit die Execute-Methode aufruft. Wenn die execute-Methode noch nicht aufgerufen wurde und eine auf CodeActivity basierende Aktivität abgebrochen wird, wird die Aktivität mit einem Status von Canceled geschlossen, und die Execute-Methode wird nicht aufgerufen.

Abbruch mithilfe von NativeActivity

Von NativeActivity abgeleitete Aktivitäten können die Cancel-Methode überschreiben, um eine benutzerdefinierte Abbruchlogik bereitzustellen. Wenn diese Methode nicht überschrieben wird, dann wird die standardmäßige Workflowabbruchlogik angewendet. Bei einem standardmäßigen Abbruch handelt es sich um den Prozess, der für eine NativeActivity auftritt, die die Cancel-Methode nicht überschreibt oder dessen Cancel-Methode die NativeActivity Cancel-Basismethode aufruft. Wenn eine Aktivität abgebrochen wird, kennzeichnet die Laufzeit die Aktivität für den Abbruch und behandelt automatisch eine bestimmte Bereinigung. Wenn die Aktivität nur über ausstehende Lesezeichen verfügt, werden die Lesezeichen entfernt, und die Aktivität wird als Canceled markiert. Alle ausstehenden untergeordneten Aktivitäten in der abgebrochenen Aktivität werden dann abgebrochen. Jeder Versuch zur Planung zusätzlicher untergeordneter Aktivitäten wird ignoriert wird, und die Aktivität wird als Canceled markiert. Wenn eine ausstehende untergeordnete Aktivität im Zustand Canceled oder Faulted abgeschlossen wird, wird die Aktivität als Canceled markiert. Beachten Sie, dass eine Abbruchanforderung ignoriert werden kann. Wenn eine Aktivität nicht über ausstehende Lesezeichen oder ausgeführte untergeordnete Aktivitäten verfügt und keine zusätzlichen Arbeitselemente plant, nachdem sie für einen Abbruch gekennzeichnet wurde, wird sie erfolgreich abgeschlossen. Dieser standardmäßige Abbruch ist für viele Szenarien ausreichen, wenn jedoch eine zusätzliche Abbruchlogik erforderlich ist, können die integrierten Abbruchaktivitäten oder benutzerdefinierte Aktivitäten verwendet werden.

Im folgenden Beispiel ist die Cancel-Überschreibung einer auf NativeActivity basierenden, benutzerdefinierten ParallelForEach-Aktivität definiert. Wenn die Aktivität abgebrochen wird, behandelt diese Überschreibung die Abbruchlogik für die Aktivität. Dieses Beispiel ist Teil des Nicht generic ParallelForEach-Beispiels.

protected override void Cancel(NativeActivityContext context)  
{  
    // If we do not have a completion condition then we can just  
    // use default logic.  
    if (this.CompletionCondition == null)  
    {  
        base.Cancel(context);  
    }  
    else  
    {  
        context.CancelChildren();  
    }  
}  

Von NativeActivity abgeleitete Aktivitäten können bestimmen, ob ein Abbruch angefordert wurde, indem die IsCancellationRequested-Eigenschaft überprüft wird und sie sich selbst als abgebrochen markieren, indem die MarkCanceled-Methode aufgerufen wird. Durch Aufrufen von MarkCanceled wird eine Aktivität nicht sofort abgeschlossen. Wie üblich schließt die Laufzeit die Aktivität ab, wenn sie keine ausstehenden Aufgaben mehr aufweist; wenn jedoch MarkCanceled aufgerufen wird, ist der abschließende Zustand Canceled anstelle von Closed.

Abbruch mithilfe von AsyncCodeActivity

Auf AsyncCodeActivity basierende Aktivitäten können auch eine benutzerdefinierte Abbruchlogik bereitstellen, indem die Cancel-Methode überschrieben wird. Wenn diese Methode nicht überschrieben wird, wird keine Abbruchbehandlung ausgeführt, wenn die Aktivität abgebrochen wird. Im folgenden Beispiel ist die Cancel-Überschreibung einer auf AsyncCodeActivity basierenden, benutzerdefinierten ExecutePowerShell-Aktivität definiert. Wenn die Aktivität abgebrochen wird, wird das gewünschte Abbruchverhalten ausgeführt.

// Called by the runtime to cancel the execution of this asynchronous activity.
protected override void Cancel(AsyncCodeActivityContext context)
{
    Pipeline pipeline = context.UserState as Pipeline;
    if (pipeline != null)
    {
        pipeline.Stop();
        DisposePipeline(pipeline);
    }
    base.Cancel(context);
}

Von AsyncCodeActivity abgeleitete Aktivitäten können bestimmen, ob ein Abbruch angefordert wurde, indem die IsCancellationRequested-Eigenschaft überprüft wird und sie sich selbst als abgebrochen markieren, indem die MarkCanceled-Methode aufgerufen wird.