Компенсация
Данный раздел относится к версии Windows Workflow Foundation 4.
Компенсация в Windows Workflow Foundation (WF) — это механизм, с помощью которого можно отменить или компенсировать выполненные ранее действия (в соответствии с логикой, определенной приложением), если они привели к возникновению ошибки. В данном разделе описывается применение компенсации в рабочих процессах.
Сравнение компенсации и транзакций
Транзакция позволяет объединить несколько операций в одну единицу работы. Использование транзакции дает приложению возможность прерывать (откатывать) все изменения, выполненные в транзакции, если во время какой-либо части обработки транзакции возникнет ошибка. Однако использование транзакций может быть неприемлемо, если работа является долговременной. Пусть, например, приложение планирования путешествия реализовано как рабочий процесс. Шагами рабочего процесса могут быть заказ авиабилетов, ожидание подтверждения диспетчера и, наконец, оплата билета. Этот процесс может занять несколько дней, и включение шагов заказа и оплаты авиабилетов в одну транзакцию непрактично. Если позднее в процессе произойдет ошибка, в таком сценарии можно воспользоваться компенсацией для отмены шага заказа в рабочем процессе.
Использование действия CompensableActivity
CompensableActivity — это базовое действие компенсации в WF. Все выполняющие работу действия, для которых может понадобиться выполнить компенсацию, помещаются в элемент Body действия CompensableActivity. В данном примере шаг бронирования при покупке авиабилета размещен в элементе Body действия CompensableActivity, а отмена бронирования помещается в обработчик CompensationHandler. Сразу за действием CompensableActivity в рабочем процессе идут два действия, ожидающие одобрения от диспетчера и выполняющие шаг приобретения билета. Если состояние ошибки вызывает отмену рабочего процесса после успешного завершения работы действия CompensableActivity, то действия в обработчике CompensationHandler планируются к выполнению, а полет отменяется.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight()
},
new ManagerApproval(),
new PurchaseFlight()
}
};
Далее показан пример рабочего процесса в XAML.
<Sequence
xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:x="https://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: билет приобретен.
Рабочий процесс успешно выполнен с состоянием: Closed.
![]() |
---|
В образцах действий этого раздела, таких как ReserveFlight , на консоли отображается их название и назначение для иллюстрации порядка, в котором действия выполняются при возникновении компенсации.
|
Компенсация рабочего процесса по умолчанию
По умолчанию, если рабочий процесс отменяется, то логика компенсации выполняется для всех подлежащих компенсации действий, которые уже успешно выполнены, но еще не были подтверждены или компенсированы.
![]() |
---|
Если действие CompensableActivity имеет состояние confirmed (т. е. подтверждено), то вызов компенсации для действия становится невозможным. Процесс подтверждения описывается далее в этом разделе. |
В данном примере исключение выдается после бронирования авиабилета, но до шага подтверждения диспетчера.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight()
},
new SimulatedErrorCondition(),
new ManagerApproval(),
new PurchaseFlight()
}
};
Этот пример является рабочим процессом в XAML.
<Sequence
xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:x="https://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();
Если вызывается рабочий процесс, исключение смоделированного условия ошибки обрабатывается ведущим приложением в OnUnhandledException, рабочий процесс отменяется, и вызывается логика компенсации.
ReserveFlight: билет зарезервирован.
SimulatedErrorCondition: вызывается исключение ApplicationException.
Необработанное исключение рабочего процесса:
System.ApplicationException: моделируемая ошибка в рабочем процессе.
CancelFlight: билет отменен.
Рабочий процесс успешно выполнен с состоянием: Отменено.
Отмена и CompensableActivity
Если действия в теле Body объекта CompensableActivity не завершены, и действие отменено, выполняются действия в обработчике CancellationHandler.
![]() |
---|
Обработчик CancellationHandler вызывается, только если действия в теле Body объекта CompensableActivity не завершены, и действие отменено. Обработчик CompensationHandler выполняется, только если действия в теле Body объекта CompensableActivity успешно завершены, после чего на действии вызвана компенсация. |
Обработчик CancellationHandler предоставляет разработчикам рабочего процесса возможность предоставить соответствующую логику отмены. В следующем примере во время выполнения Body создается исключение, а затем вызывается обработчик CancellationHandler.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new Sequence
{
Activities =
{
new ReserveFlight(),
new SimulatedErrorCondition()
}
},
CompensationHandler = new CancelFlight(),
CancellationHandler = new CancelFlight()
},
new ManagerApproval(),
new PurchaseFlight()
}
};
Этот пример является рабочим процессом в XAML.
<Sequence
xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
<CompensableActivity>
<CompensableActivity.Result>
<OutArgument
x:TypeArguments="CompensationToken" />
</CompensableActivity.Result>
<Sequence>
<c:ReserveFlight />
<c:SimulatedErrorCondition />
</Sequence>
<CompensableActivity.CancellationHandler>
<c:CancelFlight />
</CompensableActivity.CancellationHandler>
<CompensableActivity.CompensationHandler>
<c:CancelFlight />
</CompensableActivity.CompensationHandler>
</CompensableActivity>
<c:ManagerApproval />
<c:PurchaseFlight />
</Sequence>
Если вызывается рабочий процесс, исключение смоделированного условия ошибки обрабатывается ведущим приложением в OnUnhandledException, рабочий процесс отменяется, и вызывается логика отмены объекта CompensableActivity. В этом примере и логика компенсации, и логика отмены имеют одну цель — отмену забронированного авиабилета.
ReserveFlight: билет зарезервирован.
SimulatedErrorCondition: вызывается исключение ApplicationException.
Необработанное исключение рабочего процесса:
System.ApplicationException: моделируемая ошибка в рабочем процессе.
CancelFlight: билет отменен.
Рабочий процесс успешно выполнен с состоянием: Отменено.
Дополнительные сведения об отмене см. в разделе Моделирование поведения отмены в рабочих процессах.
Явная компенсация с использованием действия компенсации
В предыдущем разделе была описана неявная компенсация. Неявная компенсация может использоваться в простых сценариях, но если требуется более явный контроль над планированием обработки компенсации, можно использовать действие Compensate. Для инициации процесса компенсации с применением действия Compensate используется маркер CompensationToken действия CompensableActivity, для которого необходимо провести компенсацию. Действие 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="https://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:x="https://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.
CancelFlight: билет отменен.
Рабочий процесс успешно выполнен с состоянием: Closed.
Подтверждение компенсации
По умолчанию подлежащие компенсации действия могут быть компенсированы в любой момент после их завершения. Но в некоторых ситуациях это может быть невозможно. В предыдущем примере компенсацией для бронирования авиабилета служит отмена бронирования. Однако после выполнения перелета такой шаг компенсации уже недопустим. Подтверждение подлежащего компенсации действия вызывает действие, заданное обработчиком ConfirmationHandler. Это может использоваться, например, для освобождения любых ресурсов, которые требуются при выполнении компенсации. После подтверждения действия, которое могло быть компенсировано, его компенсация становится невозможной, и при попытке такой компенсации возникнет исключение InvalidOperationException. Если рабочий процесс завершается успешно, все неподтвержденные и некомпенсированные действия, завершенные успешно, подтверждаются в порядке, обратном порядку их завершения. В данном примере авиабилет бронируется, приобретается и завершается, после чего выполняется подтверждение действия, подлежащего компенсации. Для подтверждения действия CompensableActivity следует использовать действие Confirm и задать маркер CompensationToken действия CompensableActivity, подлежащего подтверждению.
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="https://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:x="https://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: самолет в воздухе, компенсация невозможна.
Рабочий процесс успешно выполнен с состоянием: Closed.
Вложенные действия компенсации
Действие CompensableActivity может быть помещено в раздел Body другого действия CompensableActivity. В таком случае за обработку компенсации и подтверждения дочернего действия CompensableActivity отвечает родительское действие CompensableActivity, которое должно действовать образом, соответствующим задачам рабочего процесса. Если ни подтверждение, ни компенсация не вызываются явно, то подтверждение дочернего действия CompensableActivity происходит при подтверждении родительского действия; однако при вызове компенсации для родительского действия вызов компенсации для дочернего действия не производится.
См. также
Задачи
Образец компенсируемого действия
Справочник
CompensableActivity
Compensate
Confirm
CompensationToken