Kompenzace

Kompenzace ve Windows Workflow Foundation (WF) je mechanismus, kterým lze dříve dokončenou práci vrátit zpět nebo kompenzovat (podle logiky definované aplikací), když dojde k následné chybě. Tato část popisuje, jak používat kompenzaci v pracovních postupech.

Kompenzace vs. transakce

Transakce umožňuje kombinovat více operací do jedné jednotky práce. Použití transakce dává vaší aplikaci možnost přerušit (vrátit zpět) všechny změny provedené z transakce, pokud dojde k nějakým chybám během libovolné části procesu transakce. Použití transakcí však nemusí být vhodné, pokud je práce dlouhotrvající. Aplikace pro plánování cest je například implementována jako pracovní postup. Kroky pracovního postupu se můžou skládat z rezervace letu, čekání na schválení manažera a následné platby za let. Tento proces může trvat mnoho dní a není praktické, aby kroky rezervace a platby za let probíhaly v rámci jedné transakce. V takovém případě je možné kompenzaci použít k vrácení kroku rezervace pracovního postupu zpět, pokud dojde k selhání později při zpracování.

Poznámka:

Toto téma se zabývá kompenzací v pracovních postupech. Další informace o transakcích v pracovních postupech naleznete v tématu Transakce a TransactionScope. Další informace o transakcích naleznete System.Transactions a System.Transactions.Transaction.

Používání funkce CompensableActivity

CompensableActivity je klíčovou kompenzační aktivitou v systému WF. Všechny činnosti, které vykonávají práci, jež by mohla potřebovat kompenzaci, jsou umístěny do BodyCompensableActivity. V tomto příkladu je krok rezervace letu umístěn v BodyCompensableActivity a zrušení rezervace je umístěno v CompensationHandler. Bezprostředně za krokem CompensableActivity v pracovním postupu jsou dvě aktivity, které čekají na schválení manažera a pak dokončí nákupní krok letu. Pokud chybový stav způsobí zrušení pracovního postupu po úspěšném dokončení CompensableActivity, pak se naplánují aktivity v CompensationHandler obslužné rutině a let se zruší.

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

Následující příklad je pracovní postup v JAZYCE 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>

Při vyvolání pracovního postupu se v konzole zobrazí následující výstup.

ReserveFlight: Rezervace vstupenek je rezervovaná.ManagerApproval: Bylo přijato schválení manažerem.PurchaseFlight: Letenka je zakoupena.Pracovní postup byl úspěšně dokončen se stavem: Uzavřeno.

Poznámka:

Ukázkové aktivity v tomto tématu, jako ReserveFlight, zobrazují své názvy a účely v konzole, aby pomohly ilustrovat pořadí, ve kterém se aktivity provádějí, když dojde ke kompenzaci.

Výchozí kompenzace pracovního postupu

Ve výchozím nastavení platí, že pokud je pracovní postup zrušen, logika kompenzace se spustí pro jakoukoli kompenzní aktivitu, která byla úspěšně dokončena a ještě nebyla potvrzena nebo kompenzována.

Poznámka:

CompensableActivity Jakmile je potvrzeno, nelze již uplatnit kompenzaci za aktivitu. Proces potvrzení je popsán dále v této části.

V tomto příkladu se po rezervaci letu vyvolá výjimka, ale před krokem schválení manažera.

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

Tento příklad je pracovní postup v 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();

Při vyvolání pracovního postupu se simulovaná výjimka chybového stavu zpracuje hostitelskou aplikací v OnUnhandledException, pracovní postup se zruší a vyvolá se logika kompenzace.

ReserveFlight: Lístek je rezervován.SimulatedErrorCondition: Vyvolání výjimky ApplicationException.Neošetřená výjimka:System.ApplicationException: Simulovaný chybový stav v pracovním postupu.CancelFlight: Lístek je zrušen.Pracovní postup byl úspěšně dokončen se stavem: Zrušeno.

Zrušení a kompenzovatelná činnost

Pokud nejsou aktivity v rámci BodyCompensableActivity dokončeny a aktivita je zrušena, aktivity v CancellationHandler se provedou.

Poznámka:

CancellationHandler se vyvolá pouze v případě, že aktivity v BodyCompensableActivity nejsou dokončeny a je zrušena aktivita. Provede se CompensationHandler pouze v případě, že se aktivity v Body u CompensableActivity úspěšně dokončily a následně se na aktivitu vyvolá kompenzace.

CancellationHandler dává autorům pracovního postupu příležitost poskytnout vhodnou logiku zrušení. V následujícím příkladu je během provádění Body vyhozena výjimka, a poté je vyvolán 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()
    }
};

Tento příklad je pracovní postup v 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>

Při vyvolání pracovního postupu je simulovaná výjimka stavu chyby zpracována hostitelskou aplikací v OnUnhandledException, pracovní postup je zrušen a je vyvolána logika pro zrušení CompensableActivity. V tomto příkladu má logika kompenzace a logika zrušení různé cíle. Pokud se Body úspěšně dokončilo, znamená to, že byla platební karta naúčtována a let rezervován, takže kompenzace by měla vrátit zpět oba kroky. (V tomto příkladu zrušení letu zruší poplatky za platební kartu.) Pokud je však CompensableActivity zrušeno, znamená to, že Body nebylo dokončeno, a proto je nutné, aby CancellationHandler určil, jak nejlépe zpracovat zrušení. V tomto příkladu CancellationHandler zruší poplatek za platební kartu, ale protože ReserveFlight bylo poslední aktivitou v Body, nepokouší se zrušit let. Vzhledem k tomu, že ReserveFlight byla poslední aktivitou v Body, pokud by byla úspěšně dokončena, pak by Body byla dokončena a zrušení by nebylo možné.

ChargeCreditCard: Platba kreditní kartou za let.SimulatedErrorCondition: Vyvolání výjimky typu ApplicationException.Nezachycená výjimka v pracovním postupu:System.ApplicationException: Simulovaný chybový stav v pracovním postupu.CancelCreditCard: Zrušení platby kreditní kartou.Pracovní postup byl úspěšně dokončen: Zrušeno. Další informace o zrušení naleznete v tématu Zrušení.

Explicitní kompenzace pomocí aktivity "Kompenzace"

V předchozí části byla pokryta implicitní kompenzace. Implicitní kompenzace může být vhodná pro jednoduché scénáře, ale pokud je potřeba explicitnější kontrolu nad plánováním zpracování kompenzací, Compensate lze aktivitu použít. Chcete-li zahájit proces kompenzace pomocí aktivity Compensate, použije se CompensationToken u CompensableActivity, pro kterou je požadována kompenzace. Aktivitu Compensate lze použít k zahájení kompenzace jakékoli dokončené CompensableActivity, která nebyla potvrzena nebo kompenzována. Můžete například použít aktivitu Compensate v části Catches aktivity TryCatch nebo kdykoli po dokončení CompensableActivity. V tomto příkladu se Compensate aktivita používá v Catches části TryCatch aktivity k obrácení akce objektu 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
                }
            }
        }
    }
};

Tento příklad je pracovní postup v 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>

Při vyvolání pracovního postupu se v konzole zobrazí následující výstup.

ReserveFlight: Lístek je rezervovaný.SimulatedErrorCondition: Vyvolání výjimky ApplicationException.CancelFlight: Lístek je zrušený.Pracovní postup byl úspěšně dokončen se stavem: Uzavřeno.

Potvrzení kompenzace

Ve výchozím nastavení mohou být kompenzované aktivity odměněny kdykoli po jejich dokončení. V některých scénářích to nemusí být vhodné. V předchozím příkladu bylo náhradou za rezervaci lístku její zrušení. Po dokončení letu však tento krok kompenzace již není platný. Potvrzení, že kompenzační aktivita vyvolá aktivitu určenou parametrem ConfirmationHandler. Jedním z možných využití je uvolnit jakékoli prostředky, které jsou nezbytné k provedení kompenzace. Poté, co je odškodnitelná aktivita potvrzena, není možné ji kompenzovat, a pokud se o to pokusí, je vyvolána výjimka InvalidOperationException. Po úspěšném dokončení pracovního postupu jsou všechny neověřené a nekompenzované aktivity, které byly úspěšně dokončeny, potvrzeny v opačném pořadí dokončení. V tomto příkladu je let rezervovaný, zakoupený a dokončený a pak je potvrzena kompensovatelná aktivita. Chcete-li potvrdit CompensableActivity, použijte aktivitu Confirm a zadejte CompensationTokenCompensableActivity, který chcete potvrdit.

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

Tento příklad je pracovní postup v 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>

Při vyvolání pracovního postupu se v konzole zobrazí následující výstup.

ReserveFlight: Rezervace vstupenek je rezervovaná.ManagerApproval: Bylo přijato schválení manažerem.PurchaseFlight: Letenka je zakoupena.TakeFlight: Let je dokončen.ConfirmFlight: Let byl přijat, není možné žádné kompenzace.Pracovní postup byl úspěšně dokončen se stavem: Uzavřeno.

Strukturování kompenzačních aktivit

A CompensableActivity lze umístit do Body oddílu jiného CompensableActivity. CompensableActivity nelze umístit do obslužné rutiny jiného CompensableActivity. Je odpovědností rodiče CompensableActivity zajistit, že při zrušení, potvrzení nebo kompenzaci musí být všechny podřízené aktivity, které byly úspěšně dokončeny a ještě nebyly potvrzeny nebo kompenzovány, potvrzeny nebo kompenzovány předtím, než rodič dokončí zrušení, potvrzení nebo kompenzaci. Pokud není explicitně modelováno, rodič CompensableActivity implicitně kompenzuje podřízené kompenzovatelné aktivity, pokud rodič obdržel signál ke zrušení nebo kompenzaci. Pokud nadřazený objekt obdržel potvrzovací signál, implicitně potvrdí podřízené kompenzovatelné aktivity. Pokud je logika pro zpracování zrušení, potvrzení nebo kompenzace explicitně modelována v obslužné rutině nadřazeného CompensableActivityobjektu , všechny podřízené položky, které nejsou explicitně zpracovány, se implicitně potvrdí.

Viz také