Компенсация
Компенсация в Windows Workflow Foundation (WF) — это механизм, с помощью которого ранее завершенные работы можно отменить или компенсировать (следуя логике, определенной приложением) при последующем сбое. В данном разделе описывается применение компенсации в рабочих процессах.
Сравнение компенсации и транзакций
Транзакция позволяет объединить несколько операций в одну единицу работы. Использование транзакции дает приложению возможность прерывать (откатывать) все изменения, выполненные в транзакции, если во время выполнения какой-либо части обработки транзакции возникнет ошибка. Однако использование транзакций может быть неприемлемо, если работа является долговременной. Пусть, например, приложение планирования путешествия реализовано как рабочий процесс. Шагами рабочего процесса могут быть заказ авиабилетов, ожидание подтверждения диспетчера и, наконец, оплата билета. Этот процесс может занять несколько дней, и включение шагов заказа и оплаты авиабилетов в одну транзакцию непрактично. Если позднее в процессе произойдет ошибка, в таком сценарии можно воспользоваться компенсацией для отмены шага заказа в рабочем процессе.
Примечание.
В этом разделе описывается компенсация в рабочих процессах. Дополнительные сведения о транзакциях в рабочих процессах см. в разделе "Транзакции и TransactionScope". Дополнительные сведения о транзакциях см. в разделе System.Transactions и System.Transactions.Transaction.
Использование действия 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="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:\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 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 отменяет начисление обязательства на кредитную карту, но, поскольку ReserveFlight
было последним действием в Body, попытка отмены авиабилета не выполняется. Поскольку ReserveFlight
было последним действием в Body, если оно успешно завершилось, то Body также завершилась и отмена невозможна.
ChargeCreditCard: плата за кредит карта для полета.SimulatedErrorCondition: создание объекта ApplicationException.Необработанное исключение рабочего процесса:System.ApplicationException: имитированное условие ошибки в рабочем процессе.CancelCreditCard: отмена оплаты за кредитные карта.Рабочий процесс успешно завершен с состоянием: отменено. Дополнительные сведения об отмене см. в разделе "Отмена".
Явная компенсация с использованием действия компенсации
В предыдущем разделе была описана неявная компенсация. Неявная компенсация может использоваться в простых сценариях, но если требуется более явный контроль над планированием обработки компенсации, можно использовать действие 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="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.CancelFlight: билет отменен.Рабочий процесс успешно завершен с состоянием: закрыто.
Подтверждение компенсации
По умолчанию подлежащие компенсации действия могут быть компенсированы в любой момент после их завершения. Но в некоторых ситуациях это может быть невозможно. В предыдущем примере компенсацией для бронирования авиабилета служит отмена бронирования. Однако после выполнения перелета такой шаг компенсации уже недопустим. Подтверждение подлежащего компенсации действия вызывает действие, заданное обработчиком 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="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 может быть помещено в раздел Body другого действия CompensableActivity. Объект CompensableActivity не может быть помещен в обработчик другого CompensableActivity. Родительское действие CompensableActivity обязано убедиться в том, что при отмене, подтверждении или компенсации все дочерние действия, подлежащие компенсации, которые успешно завершились и еще не подтверждены и не компенсированы, должны быть подтверждены или компенсированы перед тем, как родительское действие завершит отмену, подтверждение или компенсацию. Если это не моделируется явным образом, родительское действие CompensableActivity неявно компенсирует дочерние действия, подлежащие компенсации, если получит сигнал отмены или компенсации. Если родитель получил сигнал подтверждения, родитель неявно подтвердит дочерние действия, подлежащие компенсации. Если логика обработки отмены, подтверждения или компенсации явным образом моделируется в обработчике родительского CompensableActivity, то любое явно не обработанное дочернее действие будет неявно подтверждено.