79

新しい async/await 機能を使用して、DB を非同期的に操作しようとしています。一部のリクエストは長くなる可能性があるため、キャンセルできるようにしたいです。私が直面している問題は、TransactionScope明らかにスレッド アフィニティがあることです。タスクをキャンセルするとDispose()、間違ったスレッドで実行されるようです。

具体的には、呼び出すと、 onを含む.TestTx()次のメッセージが表示されます。AggregateExceptionInvalidOperationExceptiontask.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 には、 TransactionScopeAsyncFlowOptionパラメーターを受け取るTransactionScope の新しいコンストラクターのセットがあります。

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 に答える
4

この問題は、私がコンソール アプリケーションでコードのプロトタイプを作成していたという事実に起因していますが、これは質問には反映されていませんでした。

async/await が後にコードを実行し続ける方法awaitは、 の存在に依存し、SynchronizationContext.Currentコンソール アプリケーションにはデフォルトで 1 つがありません。つまり、現在の を使用して継続が実行されることをTaskScheduler意味しThreadPoolます別のスレッドで。

したがって、作成されたのと同じスレッドでSynchronizationContext確実に破棄される が必要です。TransactionScopeWinForms と WPF アプリケーションはデフォルトでそれを持っていますが、コンソール アプリケーションはカスタムのものを使用するか、DispatcherSynchronizationContextWPF から借用することができます。

メカニズムを詳細に説明する 2 つの優れたブログ記事を次に示します:
Await、SynchronizationContext、およびコンソール アプリ
Await、SynchronizationContext、およびコンソール アプリ: パート 2

于 2012-10-06T14:06:51.903 に答える
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 に答える