Udostępnij za pośrednictwem


Wynagrodzenie

Rekompensata w programie Windows Workflow Foundation (WF) to mechanizm, za pomocą którego wcześniej ukończona praca może zostać cofniętą lub zrekompensowana (zgodnie z logiką zdefiniowaną przez aplikację), gdy wystąpi kolejna awaria. W tej sekcji opisano sposób używania rekompensaty w przepływach pracy.

Rekompensata a transakcje

Transakcja umożliwia łączenie wielu operacji w jedną jednostkę pracy. Użycie transakcji daje aplikacji możliwość przerwania (wycofania) wszystkich zmian wykonywanych z poziomu transakcji, jeśli w jakiejkolwiek części procesu transakcji wystąpią błędy. Jednak użycie transakcji może nie być odpowiednie, jeśli praca jest długotrwała. Na przykład aplikacja planowania podróży jest implementowana jako przepływ pracy. Kroki przepływu pracy mogą składać się z rezerwacji lotu, oczekiwania na zatwierdzenie menedżera, a następnie płacenia za lot. Proces ten może potrwać wiele dni i nie jest praktyczny, aby rezerwacja i płatność za lot odbywały się w ramach tej samej transakcji. W takim scenariuszu kompensacja może zostać użyta do cofnięcia kroku rezerwacji w ramach przepływu pracy, jeśli w późniejszej części przetwarzania wystąpi awaria.

Uwaga

W tym temacie opisano rekompensatę w przepływach pracy. Aby uzyskać więcej informacji na temat transakcji w przepływach pracy, zobacz Transakcji i TransactionScope. Aby uzyskać więcej informacji na temat transakcji, zobacz System.Transactions i System.Transactions.Transaction.

Korzystanie z CompensableActivity

CompensableActivity to podstawowa aktywność odszkodowawcza w ramach WF. Wszelkie działania, które mogą wymagać rekompensaty za wykonaną pracę, są umieszczane w Body elementu CompensableActivity. W tym przykładzie etap rezerwacji podczas zakupu biletu lotniczego jest umieszczany w Body i CompensableActivity, a anulowanie rezerwacji jest umieszczane w CompensationHandler. Zaraz po CompensableActivity w procesie są dwa działania, które czekają na zatwierdzenie przez menedżera, a następnie finalizują etap zakupu biletu lotniczego. Jeśli warunek błędu powoduje anulowanie przepływu pracy po pomyślnym zakończeniu CompensableActivity, działania programu obsługi CompensationHandler są zaplanowane i lot zostanie anulowany.

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

Poniższy przykład to przepływ pracy w języku 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>

Po wywołaniu przepływu pracy następujące dane wyjściowe zostaną wyświetlone w konsoli programu .

ReserveFlight: Bilet jest zarezerwowany.ManagerApproval: Zatwierdzenie przez menedżera zostało otrzymane.PurchaseFlight: Bilet został zakupiony.Przepływ pracy zakończył się pomyślnie ze statusem: Zamknięty.

Uwaga

Przykładowe działania w tym temacie, takie jak ReserveFlight, wyświetlają na konsoli swoją nazwę i przeznaczenie, aby zilustrować kolejność wykonywania działań w przypadku wystąpienia rekompensaty.

Domyślne kompensacje przepływu pracy

Domyślnie, jeśli przepływ pracy zostanie anulowany, logika rekompensaty jest uruchamiana dla wszelkich działań, które zostały pomyślnie wykonane i nie zostały jeszcze potwierdzone lub zrekompensowane.

Uwaga

Gdy CompensableActivity zostanie potwierdzone , rekompensata za działanie nie może już zostać wywołana. Proces potwierdzenia został opisany w dalszej części tej sekcji.

W tym przykładzie zgłaszany jest wyjątek po dokonaniu rezerwacji lotu, ale przed wykonaniem kroku zatwierdzania przez menedżera.

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

Ten przykład to przepływ pracy w języku 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();

Po wywołaniu przepływu pracy symulowany wyjątek błędu jest obsługiwany przez aplikację hosta w OnUnhandledException, przepływ pracy zostaje anulowany, a uruchamiana jest logika kompensacji.

ReserveFlight: Bilet jest zarezerwowany.SimulatedErrorCondition: Wywołanie wyjątku ApplicationException.Przepływ pracy: Nieobsługiwany wyjątek:System.ApplicationException: Symulowany warunek błędu w przepływie pracy.CancelFlight: Bilet został anulowany.Przepływ pracy został pomyślnie ukończony ze stanem: Anulowano.

Anulowanie i działanie podlegające odszkodowaniu

Jeśli działania w Body z CompensableActivity nie zostały ukończone i aktywność zostanie anulowana, aktywności w CancellationHandler są wykonywane.

Uwaga

CancellationHandler jest wywoływane tylko wtedy, gdy działania w BodyCompensableActivity nie zostały ukończone, a działanie zostanie anulowane. CompensationHandler jest wykonywane tylko wtedy, gdy działania w BodyCompensableActivity zostały pomyślnie zakończone, a rekompensata zostanie następnie wywołana na to działanie.

CancellationHandler daje autorom przepływów pracy możliwość zapewnienia odpowiedniej logiki anulowania. W poniższym przykładzie podczas wykonywania Bodyzgłaszany jest wyjątek, a następnie wywoływany jest 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()
    }
};

Ten przykład to przepływ pracy w języku 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>

Kiedy przepływ pracy zostanie wywołany, symulowany wyjątek stanu błędu jest obsługiwany przez aplikację hosta w OnUnhandledException, przepływ pracy jest anulowany, a logika anulowania CompensableActivity jest wywoływana. W tym przykładzie logika kompensacji i logika anulowania mają różne cele. Jeśli Body zostało ukończone pomyślnie, oznacza to, że karta kredytowa została obciążona, a lot został zarezerwowany, więc odszkodowanie powinno unieważnić oba kroki. (W tym przykładzie anulowanie lotu powoduje automatyczne anulowanie opłat za kartę kredytową). Jeśli jednak CompensableActivity zostanie anulowana, oznacza to, że Body nie została ukończona i dlatego logika CancellationHandler musi być w stanie określić, jak najlepiej obsłużyć anulowanie. W tym przykładzie CancellationHandler anuluje opłatę za kartę kredytową, ale ponieważ ReserveFlight była ostatnią aktywnością w Body, nie próbuje anulować lotu. Ponieważ ReserveFlight było ostatnim działaniem w Body, gdyby zostało pomyślnie ukończone, to Body zostałoby ukończone i nie byłoby możliwe anulowanie.

pl-PL: ChargeCreditCard: obciążenie karty kredytowej za lot.SimulatedErrorCondition: rzucenie wyjątku ApplicationException.Nieobsługiwany wyjątek w przepływie pracy:System.ApplicationException: Warunek błędu symulowanego w przepływie pracy.CancelCreditCard: Anulowanie opłat za kartę kredytową.Przepływ pracy został pomyślnie ukończony ze stanem: Anulowano. Aby uzyskać więcej informacji na temat anulowania, zobacz Anulowanie.

Jawne kompensacje przy użyciu działania kompensowania

W poprzedniej sekcji ujęto niejawne odszkodowanie. Niejawne odszkodowanie może być odpowiednie dla prostych scenariuszy, ale jeśli wymagana jest bardziej wyraźna kontrola nad planowaniem obsługi odszkodowań, można użyć działania Compensate. Aby zainicjować proces rekompensaty za pomocą działania Compensate, używany jest CompensationToken elementu CompensableActivity, dla którego jest wymagana rekompensata. Działanie Compensate służy do inicjowania kompensacji na dowolnym ukończonym CompensableActivity, które nie zostało potwierdzone ani zrekompensowane. Na przykład działanie Compensate może być używane w sekcji Catches działania TryCatch lub w dowolnym momencie po zakończeniu CompensableActivity. W tym przykładzie działanie Compensate jest używane w sekcji Catches działania TryCatch w celu odwrócenia akcji 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
                }
            }
        }
    }
};

Ten przykład to przepływ pracy w języku 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>

Po wywołaniu przepływu pracy następujące dane wyjściowe zostaną wyświetlone w konsoli programu .

ReserveFlight: Bilet jest zarezerwowany.SimulatedErrorCondition: Wyrzucenie wyjątku ApplicationException.CancelFlight: Bilet jest anulowany.Przepływ pracy został pomyślnie ukończony ze stanem: Zamknięty.

Potwierdzanie odszkodowania

Domyślnie działania, które można skompensować, mogą być zrekompensowane w dowolnym momencie po zakończeniu. W niektórych scenariuszach może to nie być odpowiednie. W poprzednim przykładzie rekompensatą za rezerwację biletu było anulowanie rezerwacji. Jednak po zakończeniu lotu ten krok odszkodowania nie jest już ważny. Potwierdzenie działania, które można skompensować, wywołuje działanie określone przez ConfirmationHandler. Jednym z możliwych zastosowań tego rozwiązania jest umożliwienie zwolnienia wszelkich zasobów niezbędnych do wykonania rekompensaty. Po potwierdzeniu działania podlegającego rekompensacie nie jest możliwe jego zrekompensowanie, a jeśli zostanie to podjęte, zgłoszony zostanie wyjątek InvalidOperationException. Po pomyślnym zakończeniu przepływu pracy wszystkie niezatwierdzone i niezakompensowane działania, które zostały ukończone pomyślnie, zostaną potwierdzone w odwrotnej kolejności ukończenia. W tym przykładzie lot jest zarezerwowany, kupiony i zrealizowany, a następnie potwierdzono aktywność podlegającą wynagrodzeniu. Aby potwierdzić CompensableActivity, użyj działania Confirm i określ CompensationTokenCompensableActivity, aby potwierdzić.

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

Ten przykład to przepływ pracy w języku 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>

Po wywołaniu przepływu pracy następujące dane wyjściowe zostaną wyświetlone w konsoli programu .

ReserveFlight: Bilet jest zarezerwowany.ManagerApproval: otrzymano zatwierdzenie menedżera.PurchaseFlight: bilet został zakupiony.TakeFlight: lot został ukończony.ConfirmFlight: Lot został podjęty, odszkodowanie nie jest już możliwe.Przepływ pracy zakończył się pomyślnie ze stanem: Zamknięty.

Organizowanie działań odszkodowawczych

CompensableActivity można umieścić w sekcji Body innego CompensableActivity. Nie można umieścić CompensableActivity w uchwycie innego CompensableActivity. Do rodzica CompensableActivity należy upewnienie się, że gdy działania podrzędne zakończą się pomyślnie i nie są jeszcze potwierdzone ani zrekompensowane, muszą one zostać potwierdzone lub zrekompensowane przed ukończeniem anulowania, potwierdzenia lub zrekompensowania przez rodzica. Jeśli nie jest to jawnie modelowane, rodzic CompensableActivity będzie niejawnie kompensować kompensowalne działania podrzędne, jeśli rodzic otrzymał sygnał anulowania lub kompensaty. Jeśli rodzic otrzyma sygnał potwierdzenia, niejawnie potwierdzi działania dziecka, które można skompensować. Jeśli logika do obsługi anulowania, potwierdzenia lub rekompensaty jest jawnie modelowana w procedurze obsługi nadrzędnego CompensableActivity, wszystkie elementy podrzędne, które nie są jawnie obsługiwane, zostaną niejawnie potwierdzone.

Zobacz też