Get TransactionScope to work with async / await

C#TransactionscopeAsync Await

C# Problem Overview


I'm trying to integrate async/await into our service bus. I implemented a SingleThreadSynchronizationContext based on this example http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx.

And it works fine, except for one thing: TransactionScope. I await for stuff inside the TransactionScope and it breaks the TransactionScope.

TransactionScope doesn't seem to play nice with async/await, certainly because it stores things in the thread using ThreadStaticAttribute. I get this exception: > "TransactionScope nested incorrectly.".

I tried to save TransactionScope data before queuing the task and restore it before running it, but it doesn't seem to change a thing. And TransactionScope code is a mess, so it's really hard to understand what's going on there.

Is there a way to make it work? Is there some alternative to TransactionScope?

C# Solutions


Solution 1 - C#

In .NET Framework 4.5.1, there is a set of new constructors for TransactionScope that take a TransactionScopeAsyncFlowOption parameter.

According to the MSDN, it enables transaction flow across thread continuations.

My understanding is that it is meant to allow you to write code like this:

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

Solution 2 - C#

Bit late for an answer but I was having the same issue with MVC4 and I updated my project from 4.5 to 4.5.1 by right clicking on project go to properties. Select application tab change target framework to 4.5.1 and use transaction as follow.

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

Solution 3 - C#

You can use DependentTransaction created by Transaction.DependentClone() method:

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/

Solution 4 - C#

In case anyone still follows this thread...

Microsoft.Data.SqlClient v3.0.1 looks like it fixes the deadlock issues with System.Transactions in async/await functions for .NET Framework! I'm using 4.8. I used the explicit distributed transaction method to allow for parallel query execution against a single sql server by using multiple connections, but I'm sure the behavior with TransactionScope is better too.

Create a CommittableTransaction, create any number of SqlConnections that enlist a DependentTransaction created from the parent committable, execute queries on the connections in parallel, complete the dependents after query execution, then commit/rollback the parent committable.

I tested it with 3 parallel async inserts to the same db on 3 different connections. While the parent committable is in progress, I used a messagebox to prompt for commit or rollback. While the distributed transaction is active, the tables were locked. I couldn't select from them in ssms. After selecting commit or rollback, either worked as expected.

Last week with Microsoft.Data.SqlClient 3.0.0, this was impossible because the .Commit() method would deadlock in an async function. I even tried BeginCommit/EndCommit with various methods. There was even a problem enlisting dependents in async functions, but that is fixed too. Now the simplest explicit distributed transaction method works with async/await.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionYannView Question on Stackoverflow
Solution 1 - C#ZunTzuView Answer on Stackoverflow
Solution 2 - C#Atul ChaudharyView Answer on Stackoverflow
Solution 3 - C#maximpaView Answer on Stackoverflow
Solution 4 - C#Don ReynoldsView Answer on Stackoverflow