Udostępnij za pośrednictwem


Kompensata

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. Ten proces może potrwać wiele dni i nie jest praktyczny dla kroków rezerwacji i płacenia za lot do udziału w tej samej transakcji. W takim scenariuszu rekompensata może zostać użyta do cofnięcia kroku rezerwacji przepływu pracy, jeśli w dalszej 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 Transakcje i TransactionScope. Aby uzyskać więcej informacji na temat transakcji, zobacz System.Transactions i System.Transactions.Transaction.

Korzystanie z funkcji CompensableActivity

CompensableActivity jest podstawowym działaniem odszkodowawczym w programie WF. Wszelkie działania, które wykonują pracę, które mogą wymagać rekompensaty, są umieszczane w obiekcie BodyCompensableActivity. W tym przykładzie krok rezerwacji zakupu lotu jest umieszczany w BodyCompensableActivity obiekcie , a anulowanie rezerwacji jest umieszczane w obiekcie CompensationHandler. Bezpośrednio po CompensableActivity kroku w przepływie pracy są dwa działania, które oczekują na zatwierdzenie menedżera, a następnie zakończą krok zakupu pakietu testowego. Jeśli warunek błędu powoduje anulowanie przepływu pracy po pomyślnym zakończeniu CompensableActivity , działania w CompensationHandler programie obsługi 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: odebrano zatwierdzenie menedżera.PurchaseFlight: bilet jest kupowany.Przepływ pracy został ukończony pomyślnie ze stanem: Zamknięte.

Uwaga

Przykładowe działania w tym temacie, takie jak ReserveFlight wyświetlanie ich nazwy i przeznaczenie do konsoli, aby ułatwić zilustrowanie kolejności 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

Po potwierdzeniu elementu CompensableActivitynie można już wywołać rekompensaty za działanie. 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:\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();

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

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

Anulowanie i compensableActivity

Jeśli działania w obiekcie BodyCompensableActivity nie zostały ukończone i działanie zostanie anulowane, działania w obiekcie CancellationHandler zostaną wykonane.

Uwaga

Element CancellationHandler jest wywoływany tylko wtedy, gdy działania w obiekcie BodyCompensableActivity nie zostały ukończone i działanie zostanie anulowane. Element CompensationHandler jest wykonywany tylko wtedy, gdy działania w Body obiekcie CompensableActivity zostały pomyślnie ukończone, a rekompensata zostanie następnie wywołana w ramach działania.

Program CancellationHandler zapewnia autorom przepływów pracy możliwość zapewnienia odpowiedniej logiki anulowania. W poniższym przykładzie podczas wykonywania Bodyobiektu jest zgłaszany wyjątek , a następnie wywoływany CancellationHandler jest wyjątek.

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>

Po wywołaniu przepływu pracy symulowany wyjątek warunku błędu jest obsługiwany przez aplikację hosta w OnUnhandledExceptionprogramie , przepływ pracy jest anulowany, a logika CompensableActivity anulowania jest wywoływana. W tym przykładzie logika kompensacji i logika anulowania mają różne cele. Jeśli ukończono Body pomyślnie, oznacza to, że karta kredytowa została obciążona, a lot zarezerwowany, więc odszkodowanie powinno cofnąć obie kroki. (W tym przykładzie anulowanie lotu powoduje automatyczne anulowanie opłat za kartę kredytową). Jeśli CompensableActivity jednak element zostanie anulowany, oznacza Body to, że element nie został ukończony i dlatego logika CancellationHandler musi być w stanie określić, jak najlepiej obsłużyć anulowanie. W tym przykładzie polecenie CancellationHandler anuluje opłatę za kartę kredytową, ale ponieważ ReserveFlight było ostatnim działaniem w programie Body, nie próbuje anulować lotu. Ponieważ ReserveFlight było ostatnim działaniem w obiekcie Body, jeśli zostało pomyślnie ukończone, operacja zostałaby ukończona Body i nie byłoby możliwe anulowanie.

ChargeCreditCard: opłata za kartę kredytową za lot.SimulatedErrorCondition: zgłaszanie wyjątku applicationException.Nieobsługiwany wyjątek przepływu pracy:System.ApplicationException: warunek błędu symulowanego w przepływie pracy.CancelCreditCard: Anuluj opłaty za kartę kredytową.Przepływ pracy został ukończony pomyślnie 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 w przypadku prostych scenariuszy, ale jeśli wymagana jest bardziej wyraźna kontrola nad planowaniem obsługi odszkodowań, Compensate można użyć działania. Aby zainicjować proces odszkodowań w Compensate działaniu, z CompensableActivity którego jest wymagane odszkodowanie, CompensationToken jest używany. Działanie Compensate może służyć do zainicjowania odszkodowania na każdym ukończonym, CompensableActivity który nie został potwierdzony lub zrekompensowany. Na przykład Compensate działanie może być używane w Catches sekcji TryCatch działania lub w dowolnym momencie po zakończeniu CompensableActivity działania. W tym przykładzie Compensate działanie jest używane w Catches sekcji TryCatch działania 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: zgłaszanie wyjątku applicationException.CancelFlight: Bilet jest anulowany.Przepływ pracy został ukończony pomyślnie ze stanem: Zamknięte.

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 element ConfirmationHandler. Jednym z możliwych zastosowań tego rozwiązania jest umożliwienie zwolnienia wszelkich zasobów niezbędnych do wykonania rekompensaty. Po potwierdzeniu, że działanie możliwe do wyrównania nie jest możliwe, a jeśli zostanie podjęta próba InvalidOperationException zgłoszenia wyjątku. 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 ukończony, a następnie potwierdzono działanie współzadobne. Aby potwierdzić CompensableActivityelement , użyj Confirm działania i określ CompensationToken parametr , CompensableActivity 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: odebrano zatwierdzenie menedżera.PurchaseFlight: bilet jest kupowany.TakeFlight: Lot jest ukończony.ConfirmFlight: Lot został podjęty, nie ma możliwości odszkodowania.Przepływ pracy został ukończony pomyślnie ze stanem: Zamknięte.

Zagnieżdżanie działań odszkodowawczych

Element CompensableActivity można umieścić w Body sekcji innej CompensableActivity. Element CompensableActivity może nie zostać umieszczony w procedurze obsługi innego CompensableActivityelementu . Obowiązkiem rodzica CompensableActivity jest zapewnienie, że gdy zostanie anulowany, potwierdzony lub zrekompensowany, wszystkie działania, które zostały ukończone pomyślnie i nie zostały jeszcze potwierdzone lub zrekompensowane, muszą zostać potwierdzone lub zrekompensowane przed ukończeniem, potwierdzeniem lub odszkodowaniem przez rodzica. Jeśli nie jest to jawnie modelowane, element nadrzędny CompensableActivity będzie niejawnie kompensować działania, które można zrównoważyć, jeśli rodzic otrzymał sygnał anulowania lub rekompensaty. Jeśli element nadrzędny otrzymał sygnał potwierdzenia, element nadrzędny będzie niejawnie potwierdzać działania, które można skompensować. Jeśli logika do obsługi anulowania, potwierdzenia lub rekompensaty jest jawnie modelowana w procedurze obsługi elementu nadrzędnego CompensableActivity, wszystkie elementy podrzędne, które nie są jawnie obsługiwane, zostaną niejawnie potwierdzone.

Zobacz też