Dela via


Kompensation

Kompensation i Windows Workflow Foundation (WF) är den mekanism som tidigare slutfört arbete kan ångras eller kompenseras (enligt logiken som definieras av programmet) när ett efterföljande fel inträffar. I det här avsnittet beskrivs hur du använder kompensation i arbetsflöden.

Kompensation jämfört med transaktioner

Med en transaktion kan du kombinera flera åtgärder till en enda arbetsenhet. Genom att använda en transaktion kan ditt program avbryta (återställa) alla ändringar som körs inifrån transaktionen om några fel inträffar under någon del av transaktionsprocessen. Det är dock inte lämpligt att använda transaktioner om arbetet är tidskrävande. Till exempel implementeras ett reseplaneringsprogram som ett arbetsflöde. Stegen i arbetsflödet kan bestå av att boka en flygning, vänta på chefens godkännande och sedan betala för flygningen. Den här processen kan ta många dagar och det är inte praktiskt att stegen för att boka och betala för flygresan ska ingå i samma transaktion. I ett scenario som detta kan kompensation användas för att ångra bokningssteget i arbetsflödet om det uppstår ett fel senare i bearbetningen.

Anmärkning

Det här avsnittet beskriver kompensation i arbetsflöden. Mer information om transaktioner i arbetsflöden finns i Transaktioner och TransactionScope. Mer information om transaktioner finns i System.Transactions och System.Transactions.Transaction.

Användning av CompensableActivity

CompensableActivity är den huvudsakliga kompensationsaktiviteten i WF. Alla aktiviteter som utför arbete som kan behöva kompenseras placeras i Body för en CompensableActivity. I det här exemplet placeras reservationssteget för att köpa en flygresa i Body för en CompensableActivity och avbokningen av reservationen placeras i CompensationHandler. Omedelbart efter CompensableActivity i arbetsflödet finns det två aktiviteter som väntar på chefens godkännande och därefter slutförs inköpssteget för flygresan. Om ett feltillstånd gör att arbetsflödet avbryts när CompensableActivity har slutförts schemaläggs aktiviteterna i CompensationHandler-hanteraren och flygresan avbryts.

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

Följande exempel är arbetsflödet i 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>

När arbetsflödet anropas visas följande utdata i konsolen.

ReserveFlight: Biljetten är reserverad.ManagerApproval: Godkännande av chef har mottagits.PurchaseFlight: Biljetten köps.arbetsflödet har slutförts med status: Stängd.

Anmärkning

Exempelaktiviteterna i det här avsnittet, till exempel ReserveFlight, visar sina namn och syften till konsolen för att illustrera i vilken ordning aktiviteterna utförs när ersättning inträffar.

Standardkompensation för arbetsflöde

Om arbetsflödet avbryts körs som standard kompensationslogik för alla kompenserbara aktiviteter som har slutförts helt och inte redan har bekräftats eller kompenserats.

Anmärkning

När en CompensableActivitybekräftaskan kompensation för aktiviteten inte längre begäras. Bekräftelseprocessen beskrivs senare i det här avsnittet.

I det här exemplet utlöses ett undantag efter att flygresan har reserverats men före steget för godkännande av chef.

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

Det här exemplet är arbetsflödet i 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:
        {e.TerminationException.GetType().FullName}: {e.TerminationException.Message}
        """);
    }
    else
    {
        Console.WriteLine($"Workflow completed successfully with status: {e.CompletionState}.");
    }

    syncEvent.Set();
};

wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
    Console.WriteLine($"""
    Workflow Unhandled Exception:
    {e.UnhandledException.GetType().FullName}: {e.UnhandledException.Message}
    """);

    return UnhandledExceptionAction.Cancel;
};

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

När arbetsflödet anropas hanteras undantaget för simulerade felvillkor av värdprogrammet i OnUnhandledException, arbetsflödet avbryts och kompensationslogik anropas.

ReserveFlight: Biljetten är reserverad.SimulatedErrorCondition: Utlöser en ApplicationException.Undantag för ohanterat arbetsflöde:System.ApplicationException: Simulerat felvillkor i arbetsflödet.CancelFlight: Biljetten är avbokad.Arbetsflödet har slutförts med status: Avbrutet.

Annullering och kompenserbar aktivitet

Om aktiviteterna i Body för en CompensableActivity inte har slutförts och aktiviteten avbryts, körs aktiviteterna i CancellationHandler.

Anmärkning

CancellationHandler anropas endast om aktiviteterna i Body för CompensableActivity inte har slutförts och aktiviteten avbryts. CompensationHandler körs endast om aktiviteterna i Body av CompensableActivity har slutförts och ersättningen därefter anropas för aktiviteten.

CancellationHandler ger arbetsflödesförfattarna möjlighet att tillhandahålla lämplig annulleringslogik. I följande exempel utlöses ett undantag under körningen av Bodyoch sedan anropas CancellationHandler.

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()
    }
};

Det här exemplet är arbetsflödet i 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>

När arbetsflödet anropas hanteras undantaget för simulerade felvillkor av värdprogrammet i OnUnhandledException, arbetsflödet avbryts och annulleringslogik för CompensableActivity anropas. I det här exemplet har kompensationslogik och annulleringslogik olika mål. Om Body har slutförts framgångsrikt innebär det att kreditkortet har debiterats och flygresan bokats, så kompensationen bör upphäva båda stegen. (I det här exemplet avbryter avbokningen automatiskt kreditkortsavgifterna.) Men om CompensableActivity avbryts innebär det att Body inte slutfördes och därför måste logiken i CancellationHandler kunna avgöra hur du bäst hanterar annulleringen. I det här exemplet avbryter CancellationHandler kreditkortsavgiften, men eftersom ReserveFlight var den sista aktiviteten i Bodyförsöker den inte avbryta flygningen. Eftersom ReserveFlight var den sista aktiviteten i Body, om den hade slutförts framgångsrikt, skulle Body ha slutförts och ingen annullering skulle vara möjlig.

ChargeCreditCard: Debitera kreditkort för flygning.SimulatedErrorCondition: Utlöser en ApplicationException.Undantag för ohanterat arbetsflöde:System.ApplicationException: Simulerat felvillkor i arbetsflödet.CancelCreditCard: Avbryt kreditkortsavgifter.arbetsflödet har slutförts med status: Avbryts. Mer information om annullering finns i Annullering.

Explicit kompensation med hjälp av kompensationsaktiviteten

I föregående avsnitt omfattades implicit kompensation. Implicit kompensation kan vara lämplig för enkla scenarier, men om mer explicit kontroll krävs över schemaläggningen av kompensationshantering kan den Compensate aktiviteten användas. För att initiera kompensationsprocessen med Compensate-aktiviteten används CompensationToken för den CompensableActivity som ersättningen är önskad för. Den Compensate-aktiviteten kan användas för att initiera ersättning på alla slutförda CompensableActivity som inte har bekräftats eller kompenserats. Till exempel kan en Compensate aktivitet användas i avsnittet Catches i en TryCatch aktivitet eller när som helst efter att CompensableActivity har slutförts. I det här exemplet används Compensate-aktiviteten i avsnittet Catches i en TryCatch-aktivitet för att vända på åtgärden av CompensableActivity.

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
                }
            }
        }
    }
};

Det här exemplet är arbetsflödet i 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>

När arbetsflödet anropas visas följande utdata i konsolen.

ReserveFlight: Biljetten är reserverad.SimulatedErrorCondition: Utlöser en ApplicationException.CancelFlight: Biljetten är avbokad.Arbetsflödet har slutförts med status: Avslutad.

Bekräftelse av kompensation

Som standard kan kompenserbara aktiviteter kompenseras när som helst när de har slutförts. I vissa scenarier kanske detta inte är lämpligt. I föregående exempel var kompensationen för att reservera biljetten att avbryta reservationen. Men efter att flygningen har slutförts är detta kompensationssteg inte längre giltigt. Om du bekräftar den kompenserbara aktiviteten anropas aktiviteten som anges av ConfirmationHandler. En möjlig användning för detta är att tillåta att alla resurser som krävs för att utföra kompensationen frisläpps. När en kompenserbar aktivitet har bekräftats kan den inte kompenseras, och om man försöker utlöses ett InvalidOperationException-undantag. När ett arbetsflöde har slutförts bekräftas alla icke-bekräftade och icke-kompenserade kompenserbara aktiviteter som har slutförts i omvänd ordning. I det här exemplet är flygningen reserverad, köpt och slutförd, och sedan bekräftas den kompenserbara aktiviteten. Om du vill bekräfta en CompensableActivityanvänder du aktiviteten Confirm och anger CompensationToken för CompensableActivity för att bekräfta.

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
        }
    }
};

Det här exemplet är arbetsflödet i 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>

När arbetsflödet anropas visas följande utdata i konsolen.

ReserveFlight: Biljetten är reserverad.ManagerApproval: Godkännande av chef har mottagits.PurchaseFlight: Biljetten köps.TakeFlight: Flight har slutförts.ConfirmFlight: Flyg har tagits, ingen ersättning är möjlig.arbetsflödet har slutförts med status: Stängd.

Kapsling av kompensationsaktiviteter

En CompensableActivity kan placeras i avsnittet Body i en annan CompensableActivity. En CompensableActivity får inte placeras i en hanterare av en annan CompensableActivity. Det är en förälders CompensableActivity ansvar att se till att när den avbryts, bekräftas eller kompenseras, måste alla underordnade kompenserbara aktiviteter som framgångsrikt har slutförts och inte redan har bekräftats eller kompenserats bekräftas eller kompenseras innan föräldrarpossen annullerar, bekräftar eller kompenserar. Om detta inte modelleras uttryckligen kommer den överordnade CompensableActivity implicit att kompensera de underordnade kompenserbara aktiviteterna om den överordnade får signalen att avbryta eller kompensera. Om föräldern har fått bekräftelsesignalen kommer föräldern implicit att bekräfta barnets kompenserbara aktiviteter. Om logiken för att hantera annullering, bekräftelse eller kompensation uttryckligen modelleras i hanteraren för den överordnade CompensableActivity, kommer alla underordnade som inte hanteras uttryckligen att bekräftas automatiskt.

Se även