Async and Transactions

Topics: EF Runtime
Jan 10, 2013 at 10:01 PM
Edited Jan 10, 2013 at 10:02 PM

I'm assuming this is covered by this issue, but using EF6a2, if you do the following:

using (var txn = new TransactionScope())
{
    await SomeStuffAsync();
    txn.Complete();
}

You'll get an exception saying that "A TransactionScope must be disposed on the same thread that it was created".  Is there another way to do transactions while still supporting async?  If not, are there plans to address this?

Jan 11, 2013 at 6:46 AM

If there's an ambient SynchronizationContext async/await will capture it and resume work there - which is basically UI thread if you're not somewhere else or you didn't called Task.ConfigureAwait(false);.

Jan 11, 2013 at 4:37 PM

This is in a class that's called from an asp.net WebApi method.  So are you implying that I should be able to alleviate the problem by adding .ConfigureAwait(false) calls to the async calls within the transaction?  I tried that but it seemed to cause all kinds of weird behavior (MSDTC service crashing, etc).

Jan 11, 2013 at 4:41 PM

Ok, the weirdness was my fault.  I forgot to 'await' that call from my WebApi method.  But I'm still getting the exception "A TransactionScope must be disposed on the same thread that it was created" even with ConfigureAwait(false) everywhere.

Developer
Jan 11, 2013 at 6:18 PM

TransactionScope uses thread-local storage under the covers and as far as I am aware this will not work reliably with task-based async because when the code continues executing it may do so on a different thread. This is not specific to the EF implementation of async but rather a general problem with Task-based async.

Unfortunately, the EF team doesn't have the ability to update TransactionScope and it doesn't seem like it will be fixed anytime soon. We have been doing thinking around how to make a nice experience for transactions with EF without using TransactionScope but we don't have anything concrete to share yet. We also have a few bugs around how transactions and DbContext.Database.Connection work.

For now, you can do something like this as a workaround:

using (var context = new MyContext())
{
    var connection = ((IObjectContextAdapter)context).ObjectContext.Connection;
    connection.Open();

    using (var transaction = connection.BeginTransaction())
    {
        // Some async stuff
        transaction.Commit();
    }

    connection.Close();
}

 

Jan 11, 2013 at 6:51 PM
taschmidt wrote:

Ok, the weirdness was my fault.  I forgot to 'await' that call from my WebApi method.  But I'm still getting the exception "A TransactionScope must be disposed on the same thread that it was created" even with ConfigureAwait(false) everywhere.

 

No ConfigureAwait(false) will do the opposite. By default it‘s true, capturing the context. Does your SynchronizationContext.Current have not-null value when you‘re calling the method?

Jan 11, 2013 at 7:51 PM

@cincura_net: Synchronization.Current is non-null and set to an AspNetSynchronizationContext.

@ajcvickers: That workaround appears to be working fine although I had to wrap the connection.Open call in a try/catch since it may already be open.  Thanks!

Jan 12, 2013 at 7:25 AM

I'm not that familiar with threading in ASP.NET, but AFAIR AspNetSynchronizationContext preserves HttpContext.Current, but does not preserve thread. :( I think there was some class AsyncManager (or something like that) for asynchronous controllers. You might try to look at it.