79

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

具体来说,当我打电话时,我.TestTx()得到以下AggregateException内容:InvalidOperationExceptiontask.Wait ()

"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 ();
            //  ...
            //}
        }
    }
}

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

4

5 回答 5

147

在 .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();
}

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

于 2013-07-08T13:25:46.093 回答
24

我知道这是一个旧线程,但如果有人遇到 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();
} 

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

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

于 2019-01-21T21:15:08.563 回答
4

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

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

因此,只需要一个SynchronizationContext可以确保将TransactionScope其部署在它创建的同一线程上。WinForms 和 WPF 应用程序将默认拥有它,而控制台应用程序可以使用自定义的,也可以DispatcherSynchronizationContext从 WPF 借用。

这里有两篇详细解释机制的精彩博文:
Await、SynchronizationContext 和控制台应用程序
等待、SynchronizationContext 和控制台应用程序:第 2 部分

于 2012-10-06T14:06:51.903 回答
2

面向 .NET Framework 4.6+、.NET Core 2.1+ 或 .NET Standard 2.0+

考虑使用Microsoft.Data.SqlClient,它将 .NET Framework 和 .NET Core 的 System.Data.SqlClient 组件集中在一个屋檐下。如果您想使用一些较新的 SQL Server 功能,也很有用。

查看repo或从nuget中提取。

添加包后添加 using 语句:

using Microsoft.Data.SqlClient;

使用 C# 8 的示例:

// transaction scope
using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);

// connection
await using var connection = new SqlConnection(_connectionString);

// open connection asynchronously
await connection.OpenAsync();
await using var command = connection.CreateCommand();
command.CommandText = "SELECT CategoryID, CategoryName FROM Categories;";

// run command asynchronously
await using var dataReader = await command.ExecuteReaderAsync();

while (dataReader.Read())
{
    Console.WriteLine("{0}\t{1}", dataReader.GetInt32(0), dataReader.GetString(1));
}

scope.Complete();
于 2020-07-22T06:22:35.647 回答
1

是的,您必须将事务范围保持在单个线程上。由于您在异步操作之前创建事务范围,并在异步操作中使用它,因此事务范围不在单个线程中使用。TransactionScope 不是为那样使用而设计的。

我认为一个简单的解决方案是将 TransactionScope 对象和 Connection 对象的创建移到异步操作中。

更新

由于异步操作在 SqlConnection 对象内,我们无法更改它。我们能做的,就是在事务范围内登记连接。我将以异步方式创建连接对象,然后创建事务范围,并登记事务。

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.
    // ...
}
于 2012-10-04T09:53:04.753 回答