Kompenzáció

A Windows Workflow Foundation (WF) kompenzációja az a mechanizmus, amellyel a korábban elvégzett munka visszavonható vagy kompenzálható (az alkalmazás által meghatározott logikát követve), ha egy későbbi hiba történik. Ez a szakasz azt ismerteti, hogyan használható a kompenzáció a munkafolyamatokban.

Kompenzáció és tranzakciók

A tranzakciók lehetővé teszik, hogy több műveletet egyetlen munkaegységbe egyesítsenek. A tranzakció használatával az alkalmazás megszakíthatja (visszaállíthatja) a tranzakción belül végrehajtott összes módosítást, ha bármilyen hiba történik a tranzakciós folyamat bármely szakaszában. Előfordulhat azonban, hogy a tranzakciók használata nem megfelelő, ha a munka hosszú ideig fut. Egy utazástervező alkalmazás például munkafolyamatként van implementálva. A munkafolyamat lépései közé tartozhat egy járat foglalása, a vezető jóváhagyására várva, majd a járatért való fizetés. Ez a folyamat több napot is igénybe vehet, és nem praktikus, ha a foglalás és a repülőjegy kifizetése ugyanabban a tranzakcióban vesz részt. Ilyen esetben a kompenzáció felhasználható a munkafolyamat foglalási lépésének visszavonására, ha a feldolgozás későbbi szakaszában hiba történik.

Megjegyzés:

Ez a témakör a munkafolyamatok kompenzációit ismerteti. A munkafolyamatok tranzakcióiról további információt a Tranzakciók és TransactionScopea . További információ a tranzakciókról: és System.TransactionsSystem.Transactions.Transaction.

A CompensableActivity használata

CompensableActivity A WF fő kompenzációs tevékenysége. Minden olyan tevékenységet, amely esetleg kompenzálandó munkát végez, egy BodyCompensableActivity. Ebben a példában a repülőjegy vásárlásának foglalási lépése a Body-ba kerül egy CompensableActivity-ben, a foglalás lemondása pedig a CompensationHandler-be. A munkafolyamatban közvetlenül a CompensableActivity-t követő két tevékenység várnak a vezető jóváhagyására, majd befejezik a járat vásárlási lépését. Ha egy hibafeltétel miatt a munkafolyamat a CompensableActivity sikeres befejezése után törölve lesz, a rendszer ütemezi a CompensationHandler kezelő tevékenységeit, és lemondja a járatot.

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

Az alábbi példa az XAML munkafolyamata.

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

A munkafolyamat meghívásakor a következő kimenet jelenik meg a konzolon.

ReserveFlight: A jegy foglalásra van fenntartva.ManagerApproval: A menedzser jóváhagyása érkezett.PurchaseFlight: A jegy meg van vásárolva.A munkafolyamat sikeresen befejeződött a következő állapottal: Lezárva.

Megjegyzés:

A jelen témakörben szereplő mintatevékenységek, mint például ReserveFlight, a konzolon jelenítik meg a nevüket és céljukat, hogy szemléltessék a tevékenységek végrehajtásának sorrendjét a kompenzáció során.

Alapértelmezett munkafolyamat-kompenzáció

Alapértelmezés szerint a munkafolyamat megszakítása esetén a kompenzációs logika minden olyan kompenzálható tevékenységhez fut, amely sikeresen befejeződött, és még nem lett megerősítve vagy kompenzálva.

Megjegyzés:

Ha CompensableActivitymegerősítést nyer, a tevékenységért járó kompenzáció már nem igényelhető. A megerősítési folyamatot a szakasz későbbi részében ismertetjük.

Ebben a példában kivétel jelenik meg a járat foglalása után, de a felettes jóváhagyási lépése előtt.

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

Ez a példa az XAML munkafolyamata.

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

A munkafolyamat meghívásakor a szimulált hibafeltétel-kivételt a gazdaalkalmazás OnUnhandledExceptionkezeli, a munkafolyamat megszakad, és a kompenzációs logika meg lesz hívva.

ReserveFlight: A jegy lefoglalva.SimulatedErrorCondition: Egy ApplicationException kivételt generál.Munkafolyamat nem kezelt kivétel:System.ApplicationException: Szimulált hibaállapot a munkafolyamatban.CancelFlight: A jegy lemondva.A munkafolyamat sikeresen befejeződött a következő állapottal: Törölve.

Lemondás és Kompenzálható Tevékenység

Ha egy BodyCompensableActivity részében lévő tevékenységek nem fejeződtek be, és a tevékenység törlődik, a CancellationHandler tevékenységei végrehajtásra kerülnek.

Megjegyzés:

A CancellationHandler csak akkor kerül meghívásra, ha a BodyCompensableActivity tevékenységei nem fejeződtek be, és a tevékenység megszakad. A CompensationHandler végrehajtás csak akkor történik meg, ha az adott tevékenység BodyCompensableActivity sikeresen befejeződött, és a rendszer ezt követően kompenzációt hív meg a tevékenységhez.

CancellationHandler lehetővé teszi a munkafolyamat-szerzők számára, hogy bármilyen megfelelő megszakítási logikát biztosítsanak. Az alábbi példában kivétel keletkezik a Body végrehajtása során, majd meghívódik a 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()
    }
};

Ez a példa az XAML munkafolyamata

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

Amikor a munkafolyamatot meghívják, a szimulált hibaállapot-kivételt a gazdaalkalmazás kezeli OnUnhandledException, a munkafolyamat megszakad, és a CompensableActivity megszakítási logikát meghívják. Ebben a példában a kompenzációs logika és a lemondási logika különböző célokkal rendelkezik. Ha a Body művelet sikeresen befejeződött, akkor ez azt jelenti, hogy a hitelkártyát felszámolták és a járatot lefoglalták, így a kompenzációnak mindkét lépést vissza kell vonnia. (Ebben a példában a járat lemondása automatikusan törli a hitelkártyával kapcsolatos díjakat.) Ha azonban a CompensableActivity lemondás megtörtént, az azt jelenti, hogy a Body művelet nem fejeződött be, ezért a CancellationHandler logikának meg kell tudnia határozni, hogyan lehet a legjobban kezelni a lemondást. Ebben a példában a CancellationHandler hitelkártyával kapcsolatos díjakat törli, de mivel ReserveFlight ez volt az utolsó tevékenység a Bodygépen, nem próbálja meg megszakítani a járatot. Mivel a ReserveFlight volt az utolsó tevékenység a Body-ban, ha sikeresen befejeződött volna, akkor a Body is befejeződött volna, és a visszavonás nem lett volna lehetséges.

ChargeCreditCard: Hitelkártyás fizetés a repüléshez.SimulatedErrorCondition: Egy ApplicationException dobása.Munkafolyamat nem kezelt kivétel:System.ApplicationException: Szimulált hibaállapot a munkafolyamatban.CancelCreditCard: Hitelkártya-díjak lemondása.A munkafolyamat sikeresen befejeződött a következő állapottal: Megszakítva. További információ a lemondásról: Lemondás.

Kifejezett kompenzálás a kompenzáló tevékenység alkalmazásával

Az előző szakaszban implicit kompenzációról volt szó. Az implicit kompenzáció egyszerű forgatókönyvekhez megfelelő lehet, de ha explicitebb szabályozásra van szükség a kompenzációs kezelés ütemezése felett, akkor a Compensate tevékenység használható. A Compensate tevékenységgel történő kompenzációs folyamat elindításához annak a CompensationTokenCompensableActivity részét használják fel, amelyre a kompenzációt igénylik. A Compensate tevékenység arra használható, hogy kompenzációt kezdeményezzen minden olyan befejezett CompensableActivity esetében, amely nem lett megerősítve vagy kompenzálva. Egy Compensate tevékenység például használható egy Catches szakaszban egy TryCatch tevékenységnél, vagy bármikor azután, hogy a CompensableActivity befejeződött. Ebben a példában a Compensate tevékenység Catches szakaszában a TryCatch tevékenységet használjuk a CompensableActivity művelet megfordítására.

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

Ez a példa az XAML munkafolyamata.

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

A munkafolyamat meghívásakor a következő kimenet jelenik meg a konzolon.

ReserveFlight: A jegy foglalva van.SimulatedErrorCondition: ApplicationException dobása.CancelFlight: A jegy törölve lett.A munkafolyamat sikeresen befejeződött a következő állapottal: Lezárva.

Kompenzáció megerősítése

Alapértelmezés szerint a kompenzálható tevékenységek a befejezés után bármikor kompenzálhatók. Bizonyos esetekben ez nem feltétlenül megfelelő. Az előző példában a jegy lefoglalásának kompenzációja a foglalás lemondása volt. A járat befejezése után azonban ez a kompenzációs lépés már nem érvényes. A kompenzálható tevékenység megerősítése meghívja a ConfirmationHandler által meghatározott tevékenységet. Ennek egyik lehetséges célja, hogy a kompenzáció végrehajtásához szükséges erőforrásokat felszabadítsa. A kompenzálható tevékenység megerősítése után nem lehetséges a kompenzálás, és ha ez a kísérlet kivételt kísérel meg, a rendszer kivételt InvalidOperationException vet ki. Ha egy munkafolyamat sikeresen befejeződött, a sikeresen befejezett nem megerősített és nem kompenzált kompenzálandó tevékenységek a befejezés fordított sorrendjében lesznek megerősítve. Ebben a példában a járat foglalása, megvásárlása és lezajlása után a kompenzálható tevékenység megerősítést nyer. CompensableActivity megerősítéséhez használja a Confirm tevékenységet, és adja meg a CompensationToken megerősítéséhez szükséges CompensableActivity értékét.

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

Ez a példa az XAML munkafolyamata.

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

A munkafolyamat meghívásakor a következő kimenet jelenik meg a konzolon.

ReserveFlight: A jegy foglalásra van fenntartva.ManagerApproval: A menedzser jóváhagyása érkezett.PurchaseFlight: A jegy meg van vásárolva.TakeFlight: A repülés befejeződött.ConfirmFlight: A járat el lett adva, kártérítés nem lehetséges.A munkafolyamat sikeresen befejeződött a következő állapottal: Lezárva.

Kompenzációs tevékenységek szervezése

A CompensableActivity egy másik BodyCompensableActivity szakaszába helyezhető. A CompensableActivity nem helyezhető el egy másik CompensableActivity kezelőjébe. A szülő CompensableActivity felelőssége annak biztosítása, hogy a lemondás, megerősítés vagy kompenzáció során minden olyan kompenzálható tevékenységet, amely sikeresen befejeződött, és amely még nem lett megerősítve vagy kompenzálva, a lemondás, megerősítés vagy kompenzáció befejezése előtt meg kell erősíteni vagy kompenzálni. Ha ez nem kifejezetten modellezhető, a szülő CompensableActivity implicit módon kompenzálja a gyermek kompenzálható tevékenységeit, ha a szülő megkapta a megszakítási vagy kompenzáló jelet. Ha a szülő megkapta a megerősítési jelet, a szülő implicit módon megerősíti a gyermek kompenzálható tevékenységeit. Ha a lemondást, megerősítést vagy kompenzációt kezelő logikát explicit módon modellezi a szülő CompensableActivitykezelője, a nem explicit módon kezelt gyermekek implicit módon megerősítést kapnak.

Lásd még