Freigeben über


Kompensierung

Die Kompensierung in Windows Workflow Foundation (WF) ist der Mechanismus, mit dem zuvor abgeschlossene Arbeitsaufgaben rückgängig gemacht oder kompensiert werden können (gemäß der von der Anwendung definierten Logik), wenn nachfolgend ein Fehler auftritt. In diesem Abschnitt wird beschrieben, wie die Kompensation in Workflows verwendet wird.

Kompensation und Transaktionen

Eine Transaktion ermöglicht es Ihnen, mehrere Vorgänge in nur einer Arbeitseinheit zu kombinieren. 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. Die Verwendung von Transaktionen eignet sich jedoch möglicherweise nicht für Arbeitsaufgaben mit langer Laufzeit. Angenommen, eine Anwendung zum Planen von Reisen wird als Workflow implementiert. Die Schritte des Workflows bestehen z. B. aus der Buchung eines Flugs, dem Warten auf die Genehmigung des Managers und der anschließenden Bezahlung des Flugs. Da sich dieser Prozess über mehrere Tage hinziehen kann, ist es nicht sinnvoll, die Buchungs- und Zahlungsschritte in derselben Transaktion zu implementieren. In einem Szenario wie diesem kann der Buchungsschritt des Workflows mittels Kompensation rückgängig gemacht werden, wenn an späterer Stelle ein Verarbeitungsfehler auftritt.

Hinweis

Dieses Thema behandelt die Kompensation in Workflows. Weitere Informationen zur Transaktionsunterstützung in Workflows finden Sie unter Transaktionen unter TransactionScope. Weitere Informationen über Transaktionen finden Sie unter System.Transactions und System.Transactions.Transaction.

Verwenden von CompensableActivity

Das CompensableActivity ist die wichtigste Kompensationsaktivität in WF. Alle Aktivitäten zum Ausführen einer Arbeit, die möglicherweise kompensiert werden soll, werden in das Body-Element eines CompensableActivity-Objekts eingefügt. In diesem Beispiel wird der Reservierungsschritt der Buchung eines Flugs in das Body-Element eines CompensableActivity-Objekts eingefügt, und der Abbruch der Reservierung wird in das CompensationHandler-Element eingefügt. Im Workflow folgen unmittelbar auf das CompensableActivity-Objekt zwei Aktivitäten, für die eine Genehmigung durch einen Manager erfolgen muss und die dann die Buchung des Flugs abschließen. Wenn der Workflow aufgrund eines Fehlers abgebrochen wird, nachdem das CompensableActivity-Objekt erfolgreich abgeschlossen wurde, werden die Aktivitäten im CompensationHandler-Handler geplant und der Flug wird gestrichen.

Activity wf = new Sequence()
{
    Activities =
    {
        new CompensableActivity
        {
            Body = new ReserveFlight(),
            CompensationHandler = new CancelFlight()
        },
        new ManagerApproval(),
        new PurchaseFlight()
    }
};

Das folgende Beispiel ist der Workflow in XAML.

<Sequence
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <CompensableActivity>
    <CompensableActivity.Result>
      <OutArgument
         x:TypeArguments="CompensationToken" />
    </CompensableActivity.Result>
    <CompensableActivity.CompensationHandler>
      <c:CancelFlight />
    </CompensableActivity.CompensationHandler>
    <c:ReserveFlight />
  </CompensableActivity>
  <c:ManagerApproval />
  <c:PurchaseFlight />
</Sequence>

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

ReserveFlight: Das Ticket ist reserviert.ManagerApproval: Manager-Genehmigung erhalten.PurchaseFlight: Das Ticket wird gekauft.Der Workflow wurde erfolgreich mit status abgeschlossen: Geschlossen.

Hinweis

Die Beispielaktivitäten in diesem Thema z. B. ReserveFlight geben ihren Namen und Zweck auf der Konsole an, um die Reihenfolge zu veranschaulichen, in der die Aktivitäten ausgeführt werden, wenn eine Kompensation auftritt.

Standard-Workflowkompensation

Standardmäßig wird nach dem Abbruch eines Workflows die Kompensationslogik für jede kompensierbare Aktivität ausgeführt, die erfolgreich abgeschlossen wurde und noch nicht bestätigt oder kompensiert wurde.

Hinweis

Wenn ein CompensableActivitybestätigt wird, kann für die Aktivität keine Kompensation mehr aufgerufen werden. Der Bestätigungsvorgang wird weiter unten in diesem Abschnitt beschrieben.

In diesem Beispiel wird eine Ausnahme ausgelöst, nachdem der Flug reserviert wurde und bevor der Genehmigungsschritt durch den Manager erfolgen konnte.

Activity wf = new Sequence()
{
    Activities =
    {
        new CompensableActivity
        {
            Body = new ReserveFlight(),
            CompensationHandler = new CancelFlight()
        },
        new SimulatedErrorCondition(),
        new ManagerApproval(),
        new PurchaseFlight()
    }
};

Dieses Beispiel ist der Workflow in XAML.

<Sequence
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <CompensableActivity>
    <CompensableActivity.Result>
      <OutArgument
         x:TypeArguments="CompensationToken" />
    </CompensableActivity.Result>
    <CompensableActivity.CompensationHandler>
      <c:CancelFlight />
    </CompensableActivity.CompensationHandler>
    <c:ReserveFlight />
  </CompensableActivity>
  <c:SimulatedErrorCondition />
  <c:ManagerApproval />
  <c:PurchaseFlight />
</Sequence>
AutoResetEvent syncEvent = new AutoResetEvent(false);
WorkflowApplication wfApp = new WorkflowApplication(wf);

wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.TerminationException != null)
    {
        Console.WriteLine("Workflow terminated with exception:\n{0}: {1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else
    {
        Console.WriteLine("Workflow completed successfully with status: {0}.",
            e.CompletionState);
    }

    syncEvent.Set();
};

wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
    Console.WriteLine("Workflow Unhandled Exception:\n{0}: {1}",
        e.UnhandledException.GetType().FullName,
        e.UnhandledException.Message);

    return UnhandledExceptionAction.Cancel;
};

wfApp.Run();
syncEvent.WaitOne();

Wenn der Workflow aufgerufen wird, wird die simulierte Fehlerbedingungsausnahme von der Hostanwendung in OnUnhandledException behandelt, der Workflow wird abgebrochen, und die Kompensationslogik wird aufgerufen.

ReserveFlight: Das Ticket ist reserviert.SimulatedErrorCondition: Löst eine ApplicationException aus.Workflow unbehandelt Ausnahme:System.ApplicationException: Simulierte Fehlerbedingung im Workflow.CancelFlight: Das Ticket wird storniert.Workflow erfolgreich abgeschlossen mit Status: Abgebrochen.

Abbruch und CompensableActivity

Wenn die Aktivitäten in Body eines CompensableActivity-Objekts nicht abgeschlossen wurden und die Aktivität abgebrochen wird, werden die Aktivitäten in CancellationHandler ausgeführt.

Hinweis

CancellationHandler wird nur aufgerufen, wenn die Aktivitäten in Body des CompensableActivity-Objekts nicht abgeschlossen wurden und die Aktivität abgebrochen wird. CompensationHandler wird nur ausgeführt, wenn die Aktivitäten in Body des CompensableActivity-Objekts erfolgreich abgeschlossen wurden, und die Kompensation wird anschließend für die Aktivität aufgerufen.

CancellationHandler gibt Workflowautoren die Gelegenheit, eine geeignete Abbruchlogik bereitzustellen. Im folgenden Beispiel wird eine Ausnahme während der Ausführung von Body ausgelöst, und dann wird CancellationHandler aufgerufen.

Activity wf = new Sequence()
{
    Activities =
    {
        new CompensableActivity
        {
            Body = new Sequence
            {
                Activities =
                {
                    new ChargeCreditCard(),
                    new SimulatedErrorCondition(),
                    new ReserveFlight()
                }
            },
            CompensationHandler = new CancelFlight(),
            CancellationHandler = new CancelCreditCard()
        },
        new ManagerApproval(),
        new PurchaseFlight()
    }
};

Dieses Beispiel ist der Workflow in XAML.

<Sequence
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <CompensableActivity>
    <CompensableActivity.Result>
      <OutArgument
         x:TypeArguments="CompensationToken" />
    </CompensableActivity.Result>
    <Sequence>
      <c:ChargeCreditCard />
      <c:SimulatedErrorCondition />
      <c:ReserveFlight />
    </Sequence>
    <CompensableActivity.CancellationHandler>
      <c:CancelCreditCard />
    </CompensableActivity.CancellationHandler>
    <CompensableActivity.CompensationHandler>
      <c:CancelFlight />
    </CompensableActivity.CompensationHandler>
  </CompensableActivity>
  <c:ManagerApproval />
  <c:PurchaseFlight />
</Sequence>

Wenn der Workflow aufgerufen wird, wird die simulierte Fehlerbedingungsausnahme von der Hostanwendung in OnUnhandledException behandelt, der Workflow wird abgebrochen, und die Abbruchlogik von CompensableActivity wird aufgerufen. In diesem Beispiel haben die Kompensationslogik und die Abbruchlogik unterschiedliche Ziele. Wenn Body erfolgreich abgeschlossen wurde, bedeutet dies, dass die Kreditkarte belastet und der Flug gebucht wurde. Somit sollten von der Kompensation beide Schritte rückgängig gemacht werden. (In diesem Beispiel werden durch die Stornierung des Fluges automatisch die Kreditkartengebühren storniert.) Wenn das CompensableActivity jedoch storniert wird, bedeutet dies, dass das Body nicht abgeschlossen wurde. Die Logik des CancellationHandler muss also in der Lage sein zu bestimmen, wie die Stornierung am besten zu behandeln ist. In diesem Beispiel wird die Kreditkartenbelastung durch CancellationHandler storniert. Da ReserveFlight jedoch die letzte Aktivität in Body war, wird nicht versucht, den Flug zu stornieren. ReserveFlight war die letzte Aktivität in Body. Wenn diese erfolgreich abgeschlossen worden wäre, wäre Body abgeschlossen worden und kein Abbruch möglich gewesen.

ChargeCreditCard: Guthaben Karte für Flug.SimulatedErrorCondition: Löst eine ApplicationException aus.Workflow unbehandelt Ausnahme:System.ApplicationException: Simulierte Fehlerbedingung im Workflow.CancelCreditCard: Guthaben Karte Gebühren stornieren.Workflow erfolgreich abgeschlossen mit Status: Abgebrochen. Weitere Informationen zum Abbruch finden Sie unter Abbruch.

Explizite Kompensation mit Verwendung der Compensate-Aktivität

Im vorherigen Abschnitt wurde die implizite Kompensation behandelt. Eine implizite Kompensation eignet sich für einfache Szenarien. Wenn jedoch mehr explizite Steuerungsmöglichkeiten für das Planen der Kompensationsbehandlung erforderlich sind, kann die Compensate-Aktivität verwendet werden. Um den Kompensationsprozess mit der Compensate-Aktivität zu initiieren, wird das CompensationToken-Objekt des CompensableActivity-Objekts verwendet, für das eine Kompensation möglich sein soll. Mit der Compensate-Aktivität kann eine Kompensation für ein beliebiges abgeschlossenes CompensableActivity-Objekt initiiert werden, solange dieses nicht bestätigt oder kompensiert wurde. Beispielsweise kann eine Compensate-Aktivität im Abschnitt Catches einer TryCatch-Aktivität oder jederzeit nach Abschluss von CompensableActivity verwendet werden. In diesem Beispiel wird die Compensate-Aktivität im Abschnitt Catches einer TryCatch-Aktivität dazu verwendet, die Aktion von CompensableActivity rückgängig zu machen.

Variable<CompensationToken> token1 = new Variable<CompensationToken>
{
    Name = "token1",
};

Activity wf = new TryCatch()
{
    Variables =
    {
        token1
    },
    Try = new Sequence
    {
        Activities =
        {
            new CompensableActivity
            {
                Body = new ReserveFlight(),
                CompensationHandler = new CancelFlight(),
                ConfirmationHandler = new ConfirmFlight(),
                Result = token1
            },
            new SimulatedErrorCondition(),
            new ManagerApproval(),
            new PurchaseFlight()
        }
    },
    Catches =
    {
        new Catch<ApplicationException>()
        {
            Action = new ActivityAction<ApplicationException>()
            {
                Handler = new Compensate()
                {
                    Target = token1
                }
            }
        }
    }
};

Dieses Beispiel ist der Workflow in XAML.

<TryCatch
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:s="clr-namespace:System;assembly=mscorlib"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <TryCatch.Variables>
    <Variable
       x:TypeArguments="CompensationToken"
       x:Name="__ReferenceID0"
       Name="token1" />
  </TryCatch.Variables>
  <TryCatch.Try>
    <Sequence>
      <CompensableActivity>
        <CompensableActivity.Result>
          <OutArgument
             x:TypeArguments="CompensationToken">
            <VariableReference
               x:TypeArguments="CompensationToken"
               Variable="{x:Reference __ReferenceID0}">
              <VariableReference.Result>
                <OutArgument
                   x:TypeArguments="Location(CompensationToken)" />
              </VariableReference.Result>
            </VariableReference>
          </OutArgument>
        </CompensableActivity.Result>
        <CompensableActivity.CompensationHandler>
          <c:CancelFlight />
        </CompensableActivity.CompensationHandler>
        <CompensableActivity.ConfirmationHandler>
          <c:ConfirmFlight />
        </CompensableActivity.ConfirmationHandler>
        <c:ReserveFlight />
      </CompensableActivity>
      <c:SimulatedErrorCondition />
      <c:ManagerApproval />
      <c:PurchaseFlight />
    </Sequence>
  </TryCatch.Try>
  <TryCatch.Catches>
    <Catch
       x:TypeArguments="s:ApplicationException">
      <ActivityAction
         x:TypeArguments="s:ApplicationException">
        <Compensate>
          <Compensate.Target>
            <InArgument
               x:TypeArguments="CompensationToken">
              <VariableValue
                 x:TypeArguments="CompensationToken"
                 Variable="{x:Reference __ReferenceID0}">
                <VariableValue.Result>
                  <OutArgument
                     x:TypeArguments="CompensationToken" />
                </VariableValue.Result>
              </VariableValue>
            </InArgument>
          </Compensate.Target>
        </Compensate>
      </ActivityAction>
    </Catch>
  </TryCatch.Catches>
</TryCatch>

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

ReserveFlight: Das Ticket ist reserviert.SimulatedErrorCondition: Löst eine ApplicationException aus.CancelFlight: Das Ticket wird storniert.Der Workflow wurde erfolgreich mit Status abgeschlossen: Geschlossen.

Bestätigen einer Kompensation

Standardmäßig können kompensierbare Aktivitäten jederzeit kompensiert werden, nachdem sie abgeschlossen wurden. Für einige Szenarien eignet sich diese Vorgehensweise möglicherweise nicht. Im vorherigen Beispiel bestand die Kompensation bei der Reservierung des Tickets darin, den Reservierungsvorgang abzubrechen. Nachdem der Flug jedoch stattgefunden hat, ist dieser Kompensationsschritt nicht mehr gültig. Bei der Bestätigung der kompensierbaren Aktivität wird die Aktivität aufgerufen, die von ConfirmationHandler angegeben wird. Eine Verwendungsmöglichkeit dafür besteht darin, alle Ressourcen freizugeben, die für die Durchführung der Kompensation erforderlich sind. Nachdem eine kompensierbare Aktivität bestätigt wurde, kann sie nicht mehr kompensiert werden. Falls dies dennoch versucht wird, wird eine InvalidOperationException-Ausnahme ausgelöst. Wenn ein Workflow erfolgreich abgeschlossen wird, werden alle nicht bestätigten und nicht kompensierbaren Aktivitäten, die erfolgreich abgeschlossen wurden, in umgekehrter Reihenfolge Ihres Abschlusses bestätigt. In diesem Beispiel wird der Flug reserviert, gebucht und abgeschlossen, und dann wird die kompensierbare Aktivität bestätigt. Um ein CompensableActivity-Objekt zu bestätigen, verwenden Sie die Confirm-Aktivität, und geben Sie das CompensationToken-Objekt des CompensableActivity-Objekts an, das bestätigt werden soll.

Variable<CompensationToken> token1 = new Variable<CompensationToken>
{
    Name = "token1",
};

Activity wf = new Sequence()
{
    Variables =
    {
        token1
    },
    Activities =
    {
        new CompensableActivity
        {
            Body = new ReserveFlight(),
            CompensationHandler = new CancelFlight(),
            ConfirmationHandler = new ConfirmFlight(),
            Result = token1
        },
        new ManagerApproval(),
        new PurchaseFlight(),
        new TakeFlight(),
        new Confirm()
        {
            Target = token1
        }
    }
};

Dieses Beispiel ist der Workflow in XAML.

<Sequence
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Sequence.Variables>
    <x:Reference>__ReferenceID0</x:Reference>
  </Sequence.Variables>
  <CompensableActivity>
    <CompensableActivity.Result>
      <OutArgument
         x:TypeArguments="CompensationToken">
        <VariableReference
           x:TypeArguments="CompensationToken">
          <VariableReference.Result>
            <OutArgument
               x:TypeArguments="Location(CompensationToken)" />
          </VariableReference.Result>
          <VariableReference.Variable>
            <Variable
               x:TypeArguments="CompensationToken"
               x:Name="__ReferenceID0"
               Name="token1" />
          </VariableReference.Variable>
        </VariableReference>
      </OutArgument>
    </CompensableActivity.Result>
    <CompensableActivity.CompensationHandler>
      <c:CancelFlight />
    </CompensableActivity.CompensationHandler>
    <CompensableActivity.ConfirmationHandler>
      <c:ConfirmFlight />
    </CompensableActivity.ConfirmationHandler>
    <c:ReserveFlight />
  </CompensableActivity>
  <c:ManagerApproval />
  <c:PurchaseFlight />
  <c:TakeFlight />
  <Confirm>
    <Confirm.Target>
      <InArgument
         x:TypeArguments="CompensationToken">
        <VariableValue
           x:TypeArguments="CompensationToken"
           Variable="{x:Reference __ReferenceID0}">
          <VariableValue.Result>
            <OutArgument
               x:TypeArguments="CompensationToken" />
          </VariableValue.Result>
        </VariableValue>
      </InArgument>
    </Confirm.Target>
  </Confirm>
</Sequence>

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

ReserveFlight: Das Ticket ist reserviert.ManagerApproval: Manager-Genehmigung erhalten.PurchaseFlight: Das Ticket wird gekauft.TakeFlight: Der Flug ist abgeschlossen.ConfirmFlight: Flug wurde durchgeführt, keine Entschädigung möglich.Der Workflow wurde erfolgreich abgeschlossen mit Status: Geschlossen.

Schachteln von Kompensationsaktivitäten

Ein CompensableActivity-Objekt kann in den Abschnitt Body eines anderen CompensableActivity-Objekts eingefügt werden. CompensableActivity darf nicht in einen Handler einer anderen CompensableActivity eingefügt werden. Eine übergeordnete CompensableActivity muss dafür sorgen, dass im Falle eines Abbruchs, einer Bestätigung oder Kompensation alle untergeordneten kompensierbaren Aktivitäten, die erfolgreich abgeschlossen und noch nicht bestätigt oder kompensiert wurden, bestätigt oder kompensiert werden, bevor die übergeordnete Aktivität den Abbruch, die Bestätigung oder Kompensation abschließt. Wenn dies nicht explizit modelliert wird, kompensiert die übergeordnete CompensableActivity implizit untergeordnete kompensierbare Aktivitäten, wenn sie das Abbruch- oder Kompensationssignal empfängt. Nachdem die übergeordnete Aktivität das Bestätigungssignal empfangen hat, bestätigt sie implizit untergeordnete kompensierbare Aktivitäten. Wenn die Logik zur Behandlung des Abbruchs, der Bestätigung oder Kompensation explizit im Handler der übergeordneten CompensableActivity modelliert wird, wird jede untergeordnete Aktivität, die nicht explizit behandelt wird, implizit bestätigt.

Siehe auch