tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

Awaiting something in TransactionScope

12 Jun 2012 2 mins .NET, Best practice or not?, C#, Multithreading/Parallelism/Asynchronous/Concurrency

I like asynchronous programming. The Asynchronous Programming Model and later Tasks with ContinueWith offer great performance especially if no waiting and similar is used.

Although the async/await makes this very simple for 99% of cases, there’s always 1% where you might hit the wall. With callbacks it was little bit obvious, because you wrote the code. Now the compiler is doing the hard work.

If you do something like:

lock (SyncRoot)
{
	await FooBar.DoAsync();
}

You’ll get nice error from compiler saying The 'await' operator cannot be used in the body of a lock statement. And it really makes sense. The lock will very likely provide wrong results under default rewriting work the compiler is doing (and doing it properly for Monitor class needs a lot of knowledge of what you’re trying to achieve. What’s not so clear is that with TransactionScope block (or similar construct) you’re basically doing same stuff, just probably somewhere else in database.

So the compiler is completely OK with:

using (TransactionScope ts = new TransactionScope())
{
	await FooBar.DoAsync();
}

But that’s not what you might had in mind. Consider code like:

static void Main(string[] args)
{
	Test();
}
static async void Test()
{
	Task t = FooAsync();
	Console.WriteLine("Other stuff");
	await t;
}
static async Task FooAsync()
{
	using (Test t = new Test())
	{
		Console.WriteLine("Before");
		await Task.Yield();
		Console.WriteLine("After");
	}
}
class Test : IDisposable
{
	public void Dispose()
	{
		Console.WriteLine("Dispose");
	}
}

The result is correct (and expected, if you look at it closely):

Before
Other stuff
After
Dispose

But considering, that the Other stuff might have some dependency in data being done in that transaction you might get wrong result.

So it’s not always wrong, nor some gotcha in compiler. But think it through before using this construct.

Profile Picture Jiří Činčura is .NET, C# and Firebird expert. He focuses on data and business layers, language constructs, parallelism, databases and performance. For almost two decades he contributes to open-source, i.e. FirebirdClient. He works as a senior software engineer for Microsoft. Frequent speaker and blogger at www.tabsoverspaces.com.