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