Participating in TransactionScopes and Async/Await Alone in the dark

In my last post I showed you how to enable asynchronous transaction flow in .NET 4.5.1 and how you can write an enlistment which participates in the transaction scope. But something went terribly wrong. I’m sure you already spotted it. Let us have a quick look at the test execution from my previous post.

FailingTest

The test execution time clearly shows that the red test took less than a second to process and then failed. How can that be when our simulated send operation asynchronously delays its execution for 1000 milliseconds and we are effectively doing three of those sends?

In order to understand what happened we need to take again a closer look at our AsyncSendResourceManager.

public async void Commit(Enlistment enlistment)

In order to be able to await the Func<Task> onCommit we made the Commit method async. The problem is that the Commit method itself is defined by the interface IEnlistmentNotification as a method returning void. General guidelines when doing asynchronous programming with async/await say that you should always try to avoid async void methods (except for asynchronous event handlers).  Async void methods are basically fire & forget methods and therefore can’t be awaited. Furthermore async void methods have very special error handling behavior. In fact you can’t catch exceptions thrown by an async void method (this is not entirely true as we will explore in future posts). When a method which returns a Task or Task<T> throws an exception the exception is stored internally and rethrown when the task is awaited. You might be thinking to yourself: Yeah who cares about exceptions and I don’t see another way to do it so let’s move on. Hold on my friend! I’m sorry to tell you: if we’d be doing that in an infrastructure sending thousands of invoices over a message based system, we’ve possibly just lost hundreds of thousands of dollars (or even worse swiss francs 😉 ) in case the sender cannot reach the messaging infrastructure (because of network partitioning or other failures). You don’t believe a single word I’m writing? Fair enough let the code speak:

I introduce a failure condition during the asynchronous send operation.

private async Task SendInternal(Message message)
        {
            // .. as before
            throw new InvalidOperationException("Run if you still can!");
            // .. as before
        }

Now our VIP customers comes shopping

[Test]
public async Task VIPCustomerSendingBillionsOfDollars()
{
        var vipCustomer = new AsyncTransactionalMessageSender();
        using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
        {
                await vipCustomer.SendAsync(new Message("First Billion"));
                await vipCustomer.SendAsync(new Message("Second Billion"));
                await vipCustomer.SendAsync(new Message("Third Billion"));

                tx.Complete();
        }
        Debug.WriteLine("But boss we used a TransactionScope and it completed successful!");
}

ButBoss

Everything is green. But in fact we never got the billions sent by the customer. Your company doesn’t get the money, gets out of business and you stand alone in the dark.

Rest assured I’ll be your guidance through the darkness. In the next few posts I’m going to explore how you can capture the billions sent by your customers even when you use async/await and TransactionScope. Stay tuned and buy a bigger wallet in the meantime.

About the author

Daniel Marbach

2 comments

By Daniel Marbach

Recent Posts