Get TransactionScope to work with async / await
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
?
Solution 1:
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:
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:
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/