如何在可取消的async/await中部署TransactionScope?

cha*_*ase 57 .net c# database transactions async-await

我正在尝试使用新的async/await功能来异步处理数据库.由于一些请求可能很长,我希望能够取消它们.我遇到的问题是TransactionScope显然有一个线程亲和力,似乎在取消任务时,它Dispose()会在一个错误的线程上运行.

具体来说,打电话时.TestTx()我得到以下AggregateExceptionInvalidOperationExceptiontask.Wait ():

"A TransactionScope must be disposed on the same thread that it was created."
Run Code Online (Sandbox Code Playgroud)

这是代码:

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 ();
            //  ...
            //}
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:注释掉的部分是显示有一些事情要做 - 异步 - 一旦打开就连接,但不需要该代码来重现问题.

Zun*_*Tzu 102

在.NET Framework 4.5.1中,有一组用于TransactionScope新构造函数,它们采用TransactionScopeAsyncFlowOption参数.

根据MSDN,它支持跨线程延续的事务流.

我的理解是它允许你编写这样的代码:

// 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();
}
Run Code Online (Sandbox Code Playgroud)

我还没有尝试过,所以我不知道它是否会起作用.

  • 尝试过并且有效.在顶部使用EF6和事务范围. (2认同)

小智 18

我知道这是一个旧线程,但如果有人遇到问题 System.InvalidOperationException :必须在创建它的同一线程上处理 TransactionScope。

解决方案是至少升级到 .net 4.5.1 并使用如下所示的事务:

using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
   //Run some code here, like calling an async method
   await someAsnycMethod();
   transaction.Complete();
} 
Run Code Online (Sandbox Code Playgroud)

现在事务在方法之间共享。看看下面的链接。它提供了一个简单的例子和​​更多的细节

有关完整的详细信息,请查看


cha*_*ase 9

问题源于我正在控制台应用程序中对代码进行原型设计,但我没有在问题中反映这一点。

async/await 之后继续执行代码的方式await取决于 是否存在SynchronizationContext.Current,而控制台应用程序默认没有,这意味着继续使用 current 执行TaskScheduler,它是 a ThreadPool,所以它(可能?)执行在不同的线程上。

因此,我们只需要有一个SynchronizationContext可以确保TransactionScope在创建它的同一线程上进行处理的方法。WinForms 和 WPF 应用程序默认拥有它,而控制台应用程序可以使用自定义的,也可以DispatcherSynchronizationContext从 WPF 借用。

这里有两篇很棒的博客文章,详细解释了这些机制:
Await、SynchronizationContext 和控制台应用程序
Await、SynchronizationContext 和控制台应用程序:第 2 部分