2012-10-04 18 views
36

私は新しい非同期/待機機能を使用して、DBと非同期で作業しようとしています。リクエストの中には時間がかかるものがあるため、キャンセルできるようにしたいと考えています。私が実行している問題は、TransactionScopeは明らかにスレッドアフィニティを持っていることです。タスクをキャンセルすると、そのDispose()が間違ったスレッドで実行されたようです。 UPDATEDTransactionScopeをキャンセル可能async/awaitで処理する方法は?

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

:コメントアウト部分があることに何かがあるのを示すことです。ここのコードです

"A TransactionScope must be disposed on the same thread that it was created." 

.TestTx()を呼び出すとき

は具体的に、私はInvalidOperationExceptiontask.Wait()AggregateExceptionを含む、以下を得ます接続が開かれた後は非同期で接続が完了しますが、そのコードを再現する必要はありません。

答えて

4

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

方法非同期は/待つはawaitThreadPoolある現在TaskScheduler、使用して実行されます継続を意味デフォルトずつを持っていないSynchronizationContext.Current、およびコンソールアプリケーションの存在に依存した後にコードを実行し続けます、それで(、潜在的には?)は別のスレッドで実行されます。

このように、TransactionScopeが作成された同じスレッドに確実に配置されるように、SynchronizationContextを設定するだけで済みます。 WinFormsとWPFアプリケーションはデフォルトでそれを持ちますが、コンソールアプリケーションはカスタムアプリケーションを使用することも、WPFからDispatcherSynchronizationContextを借りることもできます。ここで

が詳細に力学を説明する2件の偉大なブログの記事です:.NET Frameworkの4.5.1で
Await, SynchronizationContext, and Console Apps
Await, SynchronizationContext, and Console Apps: Part 2

0

もちろん、トランザクションスコープを1つのスレッドに保つ必要があります。非同期アクションの前にトランザクションスコープを作成し、それを非同期アクションで使用するため、トランザクションスコープは単一のスレッドでは使用されません。 TransactionScopeはそのように使用するようには設計されていませんでした。

簡単な解決策私は、TransactionScopeオブジェクトとConnectionオブジェクトの作成を非同期アクションに移動することが簡単な解決策だと思います。

UPDATE

非同期アクションは、SqlConnectionオブジェクトの内部にあるので、我々はそれを変更することはできません。 できることは、enlist the connection in the transaction scopeです。私は、非同期の方法で接続オブジェクトを作成し、トランザクションスコープを作成し、トランザクションを登録します。

+0

2つ目の点を詳しく説明してください。どこに創造物を移動しますか? – chase

+0

次に、接続を非同期で開き、実際の作業負荷に対してブロッキング呼び出しを使用することをお勧めしますか?それとも、私は何かをもう一度見逃していますか – chase

+0

現時点では、接続は非同期でしか取得できません。私はあなたの例に従ってきましたが、質問を実際のワークロードで更新すると、私の回答も更新することができます。 – Maarten

76

TransactionScopeAsyncFlowOptionパラメータを取るnew constructors for 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(); 
} 

私はまだそれを試していないので、それがうまくいくかどうかはわかりません。

+2

試してみました。上にEF6とトランザクションスコープを使用する。 –

+0

完璧、ありがとう! –

+0

4.5.1にアップグレードできない場合はどうすればよいですか?その時の解決策は何ですか? – JobaDiniz

関連する問題