获取 TransactionScope 以使用 async / await

Posted

技术标签:

【中文标题】获取 TransactionScope 以使用 async / await【英文标题】:Get TransactionScope to work with async / await 【发布时间】:2012-11-24 16:47:09 【问题描述】:

我正在尝试将async/await 集成到我们的服务总线中。 我基于这个例子http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx实现了一个SingleThreadSynchronizationContext

它工作正常,除了一件事:TransactionScope。我等待TransactionScope 里面的东西,它打破了TransactionScope

TransactionScope 似乎不能与async/await 配合使用,这肯定是因为它使用ThreadStaticAttribute 在线程中存储东西。我得到了这个例外:

“TransactionScope 嵌套不正确。”。

我尝试在排队任务之前保存TransactionScope 数据并在运行它之前恢复它,但它似乎并没有改变任何事情。而且TransactionScope 的代码是一团糟,所以很难理解那里发生了什么。

有没有办法让它工作? TransactionScope 有什么替代品吗?

【问题讨论】:

这是一个非常简单的代码来重现 TransactionScope 错误pastebin.com/Eh1dxG4a,除了这里的异常是 Transaction Aborted 你能不能只使用一个普通的 SQL 事务?还是您跨越多个资源? 我跨越多个资源 看起来您需要将范围传递给您的异步方法,或者提供一种从您的工作单元标识的某种常见上下文中检索它的方法。 对于每个***TransactionScope,您需要一个带有自己的SingleThreadSynchronizationContext 的单独线程。 【参考方案1】:

在 .NET Framework 4.5.1 中,有一组 new constructors for 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();

【讨论】:

【参考方案2】:

回答有点晚了,但我在使用 MVC4 时遇到了同样的问题,我通过右键单击项目转到属性将我的项目从 4.5 更新到 4.5.1。选择应用程序选项卡将目标框架更改为 4.5.1 并使用事务如下。

using (AccountServiceClient client = new AccountServiceClient())
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))


【讨论】:

这与accepted answer?有何不同【参考方案3】:

你可以使用Transaction.DependentClone()方法创建的DependentTransaction:

static void Main(string[] args)

  // ...

  for (int i = 0; i < 10; i++)
  

    var dtx = Transaction.Current.DependentClone(
        DependentCloneOption.BlockCommitUntilComplete);

    tasks[i] = TestStuff(dtx);
  

  //...



static async Task TestStuff(DependentTransaction dtx)

    using (var ts = new TransactionScope(dtx))
    
        // do transactional stuff

        ts.Complete();
    
    dtx.Complete();

Managing Concurrency with DependentTransaction

http://adamprescott.net/2012/10/04/transactionscope-in-multi-threaded-applications/

【讨论】:

Adam Prescott 的示例子任务未标记为异步。如果你用await Task.Delay(500) 之类的东西替换“做事务性的东西”,这个模式也会因为TransactionScope nested incorrectly 而失败,因为最外面的TransactionScope(上面的例子中没有显示)在子任务正确完成之前就退出了作用域。将await 替换为Task.Wait() 就可以了,但是你失去了async 的好处。 这是一个更难解决问题的方法。 TransactionScope 是为了隐藏所有的管道。【参考方案4】:

如果有人仍然关注这个帖子......

Microsoft.Data.SqlClient v3.0.1 看起来修复了 System.Transactions 在 .NET Framework 的 async/await 函数中的死锁问题!我用的是4.8。我使用显式分布式事务方法允许通过使用多个连接对单个 sql 服务器执行并行查询,但我确信 TransactionScope 的行为也更好。

创建一个 CommittableTransaction,创建任意数量的 SqlConnections,这些 SqlConnections 征用从父可提交表创建的 DependentTransaction,并行执行连接上的查询,在查询执行后完成依赖项,然后提交/回滚父可提交表。

我在 3 个不同的连接上使用 3 个并行异步插入到同一个数据库中对其进行了测试。当父可提交正在进行时,我使用了一个消息框来提示提交或回滚。当分布式事务处于活动状态时,表被锁定。我无法在 ssms 中选择它们。选择提交或回滚后,都按预期工作。

上周使用 Microsoft.Data.SqlClient 3.0.0,这是不可能的,因为 .Commit() 方法会在异步函数中死锁。我什至用各种方法尝试过 BeginCommit/EndCommit。甚至在异步函数中招募依赖项时也会出现问题,但这也已修复。现在最简单的显式分布式事务方法适用于 async/await。

【讨论】:

我可能说得太早了关于招募家属的问题。今天经过大量测试,SqlConnection .EnlistTransaction() 似乎随机导致异常。一旦遇到异常,您将看到有关数据读取器已打开并需要关闭的消息(我根本没有在这些测试中使用它们)或由于事务在每次连续运行时都处于错误状态而无法登记。有时等待几分钟可以成功尝试,有时我必须重置 DTC 服务才能再次工作。至少没有死锁。 我的怀疑是,当你异步打开和登记一堆sql连接时,登记有时会失败,因为dtc无法正确提升事务或无法在一个之后将新连接添加到事务中或者更多的人开始执行 sql 代码。如果我在每次调用 EnlistTransaction() 后人为暂停 2 秒,让所有同时创建的连接有机会在执行查询之前登记,那么相同的代码将起作用。

以上是关于获取 TransactionScope 以使用 async / await的主要内容,如果未能解决你的问题,请参考以下文章

C#中TransactionScope的使用方法和原理

转:C#中TransactionScope的使用方法和原理

将 TransactionScope 与实体框架 6 一起使用

在 WCF 中使用 TransactionScope 回滚 SQL 事务

.Net Oracle TransactionScope的使用

TransactionScope 之分布式配置