如何在可撤销的asynchronous/等待处置TransactionScope?
我正在尝试使用新的asynchronous/等待function与数据库asynchronous工作。 由于一些请求可能是冗长的,我希望能够取消它们。 我遇到的问题是, TransactionScope
显然有一个线程亲和力,似乎取消任务时,它的Dispose()
运行在一个错误的线程。
特别是,当调用.TestTx()
我得到以下AggregateException
在task.Wait ()
上包含InvalidOperationException
:
"A TransactionScope must be disposed on the same thread that it was created."
代码如下:
public void TestTx () { var cancellation = new CancellationTokenSource (); var task = TestTxAsync ( cancellation.Token ); cancellation.Cancel (); task.Wait (); } private async Task TestTxAsync ( CancellationToken cancellationToken ) { using ( var scope = new TransactionScope () ) { using ( var connection = new SqlConnection ( m_ConnectionString ) ) { await connection.OpenAsync ( cancellationToken ); //using ( var command = new SqlCommand ( ... , connection ) ) { // await command.ExecuteReaderAsync (); // ... //} } } }
更新:注释部分是显示有一些事情要做 – asynchronous – 与连接一旦打开,但代码不需要重现的问题。
在.NET Framework 4.5.1中,有一组用于TransactionScope的新构造函数,它们使用TransactionScopeAsyncFlowOption参数。
根据MSDN,它允许跨线程延续的事务stream。
我的理解是,它是为了让你写这样的代码:
// transaction scope using (var scope = new TransactionScope(... , TransactionScopeAsyncFlowOption.Enabled)) { // connection using (var connection = new SqlConnection(_connectionString)) { // open connection asynchronously await connection.OpenAsync(); using (var command = connection.CreateCommand()) { command.CommandText = ...; // run command asynchronously using (var dataReader = await command.ExecuteReaderAsync()) { while (dataReader.Read()) { ... } } } } scope.Complete(); }
我还没有尝试过,所以我不知道它是否会起作用。
这个问题源于我在一个控制台应用程序中对代码进行原型devise,而这个问题我没有反映出来。
asynchronous/等待的方式在await
之后继续执行代码依赖于SynchronizationContext.Current
的存在,并且控制台应用程序默认没有一个,这意味着继续使用当前的TaskScheduler
(它是一个ThreadPool
,所以它( 潜在地? )在另一个线程上执行。
因此,只需要一个SynchronizationContext
来确保TransactionScope
被放置在创build的同一个线程上。 WinForms和WPF应用程序将默认使用它,而控制台应用程序可以使用自定义控件,也可以借用WPF中的DispatcherSynchronizationContext
。
这里有两个很棒的博客文章详细解释了这个机制:
等待,SynchronizationContext和控制台应用程序
等待,SynchronizationContext和控制台应用程序:第2部分
是的,你必须让你的事务范围在一个线程上。 由于您在asynchronous操作之前创build了事务处理器,并在asynchronous操作中使用它,所以事务处理器不在单个线程中使用。 TransactionScope并不是为了像那样使用而devise的。
我认为一个简单的解决scheme是将TransactionScope对象和Connection对象的创build移动到asynchronous操作中。
UPDATE
由于asynchronous操作在SqlConnection对象内,所以我们不能改变它。 我们可以做的是争夺交易范围内的联系 。 我会以asynchronous的方式创build连接对象,然后创build事务作用域,并争取事务。
SqlConnection connection = null; // TODO: Get the connection object in an async fashion using (var scope = new TransactionScope()) { connection.EnlistTransaction(Transaction.Current); // ... // Do something with the connection/transaction. // Do not use async since the transactionscope cannot be used/disposed outside the // thread where it was created. // ... }