Programing

취소 가능한 비동기 / 대기에서 TransactionScope를 처리하는 방법은 무엇입니까?

lottogame 2021. 1. 7. 07:35
반응형

취소 가능한 비동기 / 대기에서 TransactionScope를 처리하는 방법은 무엇입니까?


새로운 비동기 / 대기 기능을 사용하여 DB와 비동기 적으로 작업하려고합니다. 일부 요청이 길어질 수 있으므로 취소 할 수 있기를 바랍니다. 내가 겪고있는 문제는 TransactionScope분명히 스레드 선호도가 있으며 작업을 취소하면 Dispose()잘못된 스레드에서 실행되는 것 같습니다 .

특히 전화 .TestTx()하면 다음이 AggregateException포함 InvalidOperationException됩니다 task.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 ();
            //  ...
            //}
        }
    }
}

업데이트 됨 : 주석 처리 된 부분은 연결이 열리면 비동기 적으로 수행해야 할 작업이 있음을 보여주는 것이지만 해당 코드는 문제를 재현하는 데 필요하지 않습니다.


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

나는 아직 그것을 시도하지 않았기 때문에 그것이 효과가 있는지 모르겠습니다.


문제는 내가 콘솔 애플리케이션에서 코드를 프로토 타이핑하고 있다는 사실에서 비롯되었는데, 질문에 반영하지 않았습니다.

The way async/await continues to execute the code after await is dependent on the presence of SynchronizationContext.Current, and console application don't have one by default, which means the continuation gets executed using the current TaskScheduler, which is a ThreadPool, so it (potentially?) executes on a different thread.

Thus one simply needs to have a SynchronizationContext that will ensure TransactionScope is disposed on the same thread it was created. WinForms and WPF applications will have it by default, while console applications can either use a custom one, or borrow DispatcherSynchronizationContext from WPF.

Here are two great blog posts that explain the mechanics in detail:
Await, SynchronizationContext, and Console Apps
Await, SynchronizationContext, and Console Apps: Part 2


I know this is an old thread, but if anyone has run into the problem System.InvalidOperationException : A TransactionScope must be disposed on the same thread that it was created.

The solution is to upgrade to .net 4.5.1 at a minimum and use a transaction like the following:

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

Now the transaction is shared between methods. Take a look at the link below. It provide a simple example and more detail

For complete details, take a look at This


Yep, you have to keep you transactionscope on a single thread. Since you are creating the transactionscope before the async action, and use it in the async action, the transactionscope is not used in a single thread. The TransactionScope was not designed to be used like that.

A simple solution I think would be to move the creation of the TransactionScope object and the Connection object into the async action.

UPDATE

Since the async action is inside the SqlConnection object, we cannot alter that. What we can do, is enlist the connection in the transaction scope. I would create the connection object in an async fashion, and then create the transaction scope, and enlist the transaction.

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.
    // ...
}

ReferenceURL : https://stackoverflow.com/questions/12724529/how-to-dispose-transactionscope-in-cancelable-async-await

반응형