如何在可撤销的asynchronous/等待处置TransactionScope?

我正在尝试使用新的asynchronous/等待function与数据库asynchronous工作。 由于一些请求可能是冗长的,我希望能够取消它们。 我遇到的问题是, TransactionScope显然有一个线程亲和力,似乎取消任务时,它的Dispose()运行在一个错误的线程。

特别是,当调用.TestTx()我得到以下AggregateExceptiontask.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. // ... }