尽管有 TryCatch 活动,异常仍会从工作流中逸出
我在 Windows 服务中有一个工作流程,它是一个定期执行工作的循环。这项工作是在 TryCatch
活动。
尝试
< /a> 属性是 TransactionScope
活动,它包装了一些读取和更新数据库的自定义活动。当事务失败时,我希望
TryCatch
捕获导致此事务的任何异常。但是,我的工作流程中止了。我的工作流程如下:
var wf = new While(true)
{
Body = new Sequence
{
Activities =
{
new TryCatch
{
Try = new TransactionScope
{
IsolationLevel = IsolationLevel.ReadCommitted,
Body = new Sequence
{
Activities = { ..custom database activities.. }
},
AbortInstanceOnTransactionFailure = false
},
Catches =
{
new Catch<Exception>
{
Action = new ActivityAction<Exception>
{
Argument = exception,
Handler = ..log error..
}
}
}
},
new Delay { Duration = new InArgument<TimeSpan>(duration) }
}
},
}
就我而言,数据库有时可能不可用,因此显然事务不会提交。在这种情况下,工作流程会中止,但出现以下异常:
System.OperationCanceledException:处理当前工作项时发生错误导致工作流中止。
内部异常是:
System.Transactions.TransactionException:该操作对于事务状态无效。
这是有道理的,因为我刚刚关闭了数据库。但是,为什么我的 TryCatch
活动不处理此异常?
编辑1:一些附加信息。我使用 WorkflowApplication
运行工作流 类。为了更好地了解发生的情况,我指定了属性 <代码>中止和 OnUnhandledException
。当异常发生时,直接进入Aborted
并跳过OnUnhandledException
(尽管这显然是一个未处理的异常)。
编辑2:我启用了调试日志,这提供了一些额外的见解。 “自定义数据库活动”成功运行完成。指示出现问题的第一个事件日志条目是详细级别消息:运行时事务已完成,状态为“已中止”。接下来,我看到一条信息消息:WorkflowInstance Id: 'dbd1ba5c-2d8a-428c-970d-21215d7e06d9' E2E Activity(不确定这意味着什么)。之后的信息消息为:Activity 'System.Activities.Statements.TransactionScope', DisplayName: 'Transaction for run立即检查', InstanceId: '389' 已在“故障”状态下完成。
在此消息之后,我看到每个父级(包括 TryCatch
活动)都以“故障”状态完成,并以工作流程中止结束。
编辑 3:需要明确的是,当任何“自定义数据库活动”中发生异常时,一切都会按预期运行。捕获异常并继续工作流程。仅当事务无法在 TransactionScope
末尾提交时才会出错。请参阅从 Aborted
回调记录的以下堆栈跟踪:
at System.Transactions.TransactionStateInDoubt.Rollback(InternalTransaction tx, Exception e)
at System.Transactions.Transaction.Rollback(Exception e)
at System.Activities.Runtime.ActivityExecutor.CompleteTransactionWorkItem.HandleException(Exception exception)
如果您遵循 TransactionScope.OnCompletion(...)
的调用,最终您将到达 堆栈跟踪中的 ActivityExecutor
类。
I have a workflow inside a Windows Service that is a loop that performs work periodically. The work is done inside a TryCatch
activity. The Try
property is a TransactionScope
activity that wraps some custom activities that read and update a database. When the transaction fails, I would expect any exception that caused this to be caught by the TryCatch
. However, my workflow aborts. The workflow I have is the following:
var wf = new While(true)
{
Body = new Sequence
{
Activities =
{
new TryCatch
{
Try = new TransactionScope
{
IsolationLevel = IsolationLevel.ReadCommitted,
Body = new Sequence
{
Activities = { ..custom database activities.. }
},
AbortInstanceOnTransactionFailure = false
},
Catches =
{
new Catch<Exception>
{
Action = new ActivityAction<Exception>
{
Argument = exception,
Handler = ..log error..
}
}
}
},
new Delay { Duration = new InArgument<TimeSpan>(duration) }
}
},
}
In my case, it's possible that the database is sometimes unavailable so obviously the transaction won't commit. What happens in this case is that the workflow aborts with the following exception:
System.OperationCanceledException: An error processing the current work item has caused the workflow to abort.
The inner exception is:
System.Transactions.TransactionException: The operation is not valid for the state of the transaction.
This makes sense because I have just switched off the database. However, why isn't this exception handled by my TryCatch
activity?
EDIT 1: Some additional information. I run the workflow using the WorkflowApplication
class. To better see what's going on, I specified the properties Aborted
and OnUnhandledException
. When the exception occurs, it goes directly to Aborted
and OnUnhandledException
is skipped (although this is clearly an unhandled exception).
EDIT 2: I enabled the debug log and this provides some additional insight. The 'custom database activities' successfully run to completion. The first event log entry that indicates that something is wrong is a Verbose level message: The runtime transaction has completed with the state 'Aborted'. Next I see an Information message: WorkflowInstance Id: 'dbd1ba5c-2d8a-428c-970d-21215d7e06d9' E2E Activity (not sure what this means). And the Information message after that is: Activity 'System.Activities.Statements.TransactionScope', DisplayName: 'Transaction for run immediately checks', InstanceId: '389' has completed in the 'Faulted' state.
After this message, I see that each parent (including the TryCatch
activity) completes in the 'Faulted' state, ending with the abortion of my workflow.
EDIT 3: To be clear, everything works as expected when an exception occurs in any of the 'custom database activities'. The exception is caught and the workflow continues. It only goes wrong when the transaction can't commit at the end of the TransactionScope
. See the following stacktrace that is logged from the Aborted
callback:
at System.Transactions.TransactionStateInDoubt.Rollback(InternalTransaction tx, Exception e)
at System.Transactions.Transaction.Rollback(Exception e)
at System.Activities.Runtime.ActivityExecutor.CompleteTransactionWorkItem.HandleException(Exception exception)
If you follow the calls from TransactionScope.OnCompletion(...)
, eventually you will arrive at the ActivityExecutor
class from the stacktrace.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
事务在事后异步提交。由于资源管理器级别的问题,您无法对事务提交失败做出反应。
正如您所指出的,您可以处理活动中发生的异常情况。如果您查看工作流程的跟踪记录,我的猜测是您会看到 TryCatch 活动在事务中止之前关闭。
许多年前,当我担任 COM+ 团队的项目经理时,我研究了这个问题,因为人们通常希望事务组件(或工作流)能够对事务中止做出反应,就像本例一样。
事务解析的异步性质意味着您根本无法在组件本身中对其做出反应。解决方案是在调用者中做出反应,然后调用者可以采取一些操作。
设计假设是,一旦事务中止,则事务中获取的状态中的任何内容都无法安全使用 - 由于事务中止,所有状态都将被丢弃。
Transactions commit asynchronously and after the fact. You can't react to a failure of the transaction to commit because of a problem at the resource manager level.
As you pointed out, you can deal with exceptions that occur in your activities. If you look at the tracking records for your workflow my guess is that you would see the TryCatch activity is closed prior to the transaction abort.
Many years ago when I was a program manager in the COM+ team I studied this issue because often people want a transactional component (or workflow) as in this case to be able to react to a transaction abort.
The async nature of the resolution of the transaction means that you simply cannot react to it in the component itself. The solution is to react in the caller which can then take some action.
The design assumption is that once a transaction has aborted, nothing about state aqcuired in the transaction can be safely used - it will all be discarded because the transaction is aborted.
所以只是补充一下罗恩的答案。这里唯一的选择是添加 SqlWorkflowInstanceStore 并在 TransactionScope 之前删除 Persist 活动。当事务中止时,整个工作流将中止,但过去保存的状态仍将在持久性数据库中,并且可以从先前保存的状态重新启动工作流并再次执行事务。
So just to add to Ron's answer. Your only option here is to add the SqlWorkflowInstanceStore and drop a Persist activity just before the TransactionScope. When the transaction aborts the whole workflow will abort but the past saved state will still be in the persistence database and the workflow can be restarted from this previously saved state and execute the transaction again.