다음을 통해 공유


보상

WF(Windows Workflow Foundation)의 보정은 후속 오류가 발생할 때 이전에 완료된 작업을 실행 취소하거나 보정할 수 있는 메커니즘입니다(애플리케이션에서 정의한 논리에 따라). 이 섹션에서는 워크플로에서 보정을 사용하는 방법을 설명합니다.

보상 및 트랜잭션

트랜잭션을 사용하면 여러 작업을 단일 작업 단위로 결합할 수 있습니다. 트랜잭션을 사용하면 트랜잭션 프로세스의 일부에서 오류가 발생하는 경우 애플리케이션에서 트랜잭션 내에서 실행된 모든 변경 내용을 중단(롤백)할 수 있습니다. 그러나 작업이 오래 실행되는 경우 트랜잭션을 사용하는 것이 적절하지 않을 수 있습니다. 예를 들어 여행 계획 애플리케이션은 워크플로로 구현됩니다. 워크플로의 단계는 항공편 예약, 관리자 승인 대기 및 항공편 결제로 구성됩니다. 이 프로세스는 며칠이 걸릴 수 있으며 항공편의 예약 및 지불 단계가 동일한 거래에 참여하는 것은 실용적이지 않습니다. 이와 같은 시나리오에서는 나중에 처리에 오류가 있는 경우 보정을 사용하여 워크플로의 예약 단계를 실행 취소할 수 있습니다.

비고

이 항목에서는 워크플로의 보정에 대해 설명합니다. 워크플로의 트랜잭션에 대한 자세한 내용은 트랜잭션TransactionScope를 참조하세요. 트랜잭션에 대한 자세한 내용은 System.TransactionsSystem.Transactions.Transaction를 참조하세요.

CompensableActivity 사용

CompensableActivity 는 WF의 핵심 보정 활동입니다. 보상이 필요할 수 있는 작업을 수행하는 모든 활동은 BodyCompensableActivity에 배치됩니다. 이 예제에서는 항공편 구매의 예약 단계가 BodyCompensableActivity에 포함되고, 예약 취소는 CompensationHandler에 포함됩니다. 워크플로의 CompensableActivity 바로 다음에는 관리자 승인을 기다린 다음 플라이트 구매 단계를 완료하는 두 가지 활동이 있습니다. 오류 조건으로 인해 CompensableActivity가 성공적으로 완료된 후에도 워크플로가 취소되는 경우, 처리기에서 CompensationHandler의 활동이 예약되고 플라이트가 취소됩니다.

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

다음 예제는 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>

워크플로가 호출되면 콘솔에 다음 출력이 표시됩니다.

ReserveFlight: 티켓이 예약되어 있습니다.ManagerApproval: 관리자 승인이 수신되었습니다.PurchaseFlight: 티켓을 구매합니다.워크플로가 완료되었으며 상태가 닫혔습니다.

비고

이 항목의 샘플 활동(예: ReserveFlight 보상이 발생할 때 활동이 실행되는 순서를 설명하는 데 도움이 되도록 본체에 이름 및 용도 표시).

기본 워크플로 보상

기본적으로 워크플로가 취소된 경우 보상 논리는 완전히 성공적으로 완료되었으며 아직 확인되거나 보정되지 않은 보정 가능한 작업에 대해 실행됩니다.

비고

CompensableActivity 되면 활동에 대한 보정을 더 이상 호출할 수 없습니다. 확인 프로세스는 이 섹션의 뒷부분에 설명되어 있습니다.

이 예제에서는 플라이트를 예약한 후 관리자 승인 단계 전에 예외가 발생합니다.

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

이 예제는 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();

워크플로가 호출되면 시뮬레이션된 오류 조건 예외가 호스트 애플리케이션에서 OnUnhandledException처리되고, 워크플로가 취소되고, 보정 논리가 호출됩니다.

ReserveFlight: 티켓이 예약되어 있습니다.SimulatedErrorCondition: ApplicationException을 throw합니다.워크플로 처리되지 않은 예외:System.ApplicationException: 워크플로의 시뮬레이션된 오류 조건입니다.CancelFlight: 티켓이 취소되었습니다.워크플로가 완료되었으며 상태가 취소되었습니다.

취소 및 보상 가능 활동

BodyCompensableActivity에 있는 활동이 완료되지 않고 취소되는 경우, CancellationHandler에 있는 활동이 실행됩니다.

비고

CancellationHandlerBodyCompensableActivity 내 활동이 완료되지 않고 활동이 취소된 경우에만 호출됩니다. CompensationHandlerBodyCompensableActivity 내 활동이 성공적으로 완료되고 나서 해당 활동에 대해 보상이 호출되는 경우에만 실행됩니다.

워크플로 CancellationHandler 작성자에게 적절한 취소 논리를 제공할 수 있는 기회를 제공합니다. 다음 예제에서는 Body의 실행 중에 예외가 throw된 후 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()
    }
};

이 예제는 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>

워크플로가 호출되면 시뮬레이션된 오류 조건 예외가 호스트 애플리케이션에서 OnUnhandledException처리되고, 워크플로가 취소되고, 취소 논리가 CompensableActivity 호출됩니다. 이 예제에서는 보정 논리와 취소 논리의 목표가 다릅니다. Body 성공적으로 완료되면 신용 카드가 청구되고 항공편이 예약되었으므로 보상은 두 단계를 모두 취소해야 합니다. (이 예제에서는 항공편을 취소하면 신용 카드 요금이 자동으로 취소됩니다.) 그러나 만약 CompensableActivity가 취소된다면, 이는 Body이 완료되지 않았음을 의미하므로 CancellationHandler의 논리는 취소를 가장 잘 처리하는 방법을 결정할 수 있어야 합니다. 이 예제에서는 CancellationHandler가 신용 카드 요금을 취소하지만, ReserveFlightBody에서 마지막 활동이었으므로 항공편을 취소하려고 시도하지 않습니다. ReserveFlight 의 마지막 활동Body이었기 때문에, 성공적으로 완료 Body 되었다면 완료되었을 것이며 취소가 불가능할 것입니다.

ChargeCreditCard: 항공편에 대한 신용 카드를 청구합니다.SimulatedErrorCondition: ApplicationException을 throw합니다.워크플로 처리되지 않은 예외:System.ApplicationException: 워크플로의 시뮬레이션된 오류 조건입니다.CancelCreditCard: 신용 카드 요금을 취소합니다.워크플로가 완료되었으며 상태가 취소되었습니다. 취소에 대한 자세한 내용은 취소를 참조 하세요.

보상 작업을 사용하여 명시적 보상

이전 섹션에서는 암시적 보정을 다루었습니다. 암시적 보정은 간단한 시나리오에 적합할 수 있지만 보정 처리 Compensate 예약에 대해 보다 명시적인 제어가 필요한 경우 활동을 사용할 수 있습니다. 보상 절차를 Compensate 활동과 함께 시작하려면 보상이 필요한 CompensationTokenCompensableActivity을(를) 사용합니다. 확인되거나 보상되지 않은 완료된 Compensate에 대해 보상을 시작하려면 이 CompensableActivity 활동을 사용할 수 있습니다. 예를 들어, Compensate 활동은 Catches 섹션의 TryCatch 활동에서 사용될 수 있으며, CompensableActivity가 완료된 후 언제든지 사용할 수 있습니다. 이 예제에서는 Compensate 활동의 Catches 섹션에서 TryCatch 활동이 사용되어 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
                }
            }
        }
    }
};

이 예제는 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>

워크플로가 호출되면 콘솔에 다음 출력이 표시됩니다.

ReserveFlight: 티켓이 예약되어 있습니다.SimulatedErrorCondition: ApplicationException을 throw합니다.CancelFlight: 티켓이 취소되었습니다.워크플로가 완료되었으며 상태가 닫혔습니다.

보상 확인

기본적으로 보상 가능한 활동은 완료된 후 언제든지 보정할 수 있습니다. 일부 시나리오에서는 적절하지 않을 수 있습니다. 이전 예제에서 티켓 예약에 대한 보상은 예약을 취소하는 것이었습니다. 그러나 플라이트를 완료한 후에는 이 보정 단계가 더 이상 유효하지 않습니다. 보상 가능한 활동을 확인하면 .에 지정된 ConfirmationHandler활동이 호출됩니다. 이에 사용할 수 있는 한 가지 방법은 보정을 수행하는 데 필요한 모든 리소스를 해제할 수 있도록 하는 것입니다. 보상 가능한 활동이 확인되면 이를 보상하는 것이 불가능해지며, 만약 이를 시도하면 InvalidOperationException 예외가 발생합니다. 워크플로가 성공적으로 완료되면 성공적으로 완료된 모든 비확인 및 보상되지 않은 보상 가능한 활동이 완료 순서대로 확인됩니다. 이 예제에서는 플라이트를 예약, 구매 및 완료한 다음, 보상 가능한 활동이 확인됩니다. CompensableActivity을(를) 확인하려면 Confirm 활동을 사용하고 확인할 CompensationTokenCompensableActivity을(를) 지정하십시오.

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

이 예제는 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>

워크플로가 호출되면 콘솔에 다음 출력이 표시됩니다.

ReserveFlight: 티켓이 예약되어 있습니다.ManagerApproval: 관리자 승인이 수신되었습니다.PurchaseFlight: 티켓을 구매합니다.TakeFlight: 비행이 완료되었습니다.ConfirmFlight: 비행이 수행되었으며 보상이 불가능합니다.워크플로가 완료되었으며 상태가 닫혔습니다.

계층적 보상 활동

CompensableActivity는 다른 BodyCompensableActivity 섹션에 배치될 수 있습니다. 다른 CompensableActivity 처리기 안에 CompensableActivity을 배치할 수 없습니다. CompensableActivity 부모는 취소, 확인 또는 보상 과정 중 성공적으로 완료되었으나 아직 확인되거나 보상되지 않은 모든 자식 보상 활동을 부모의 취소, 확인 또는 보상이 완료되기 전에 반드시 확인하거나 보상해야 합니다. 명시적으로 모델링되지 않은 경우 부모는 CompensableActivity 취소 또는 보정 신호를 받은 경우 자식 보상 가능 활동을 암시적으로 보정합니다. 부모가 확인 신호를 받은 경우 부모는 자식 보상 가능 활동을 암시적으로 확인합니다. 취소, 확인 또는 보정을 처리하는 논리가 부모의 CompensableActivity처리기에서 명시적으로 모델링되는 경우 명시적으로 처리되지 않은 모든 자식은 암시적으로 확인됩니다.

참고하십시오