I'm doing some stuff inside a using block for a TransactionScope object. At some point I wanted to call some async code by firing and forget (I don't want to wait for the result, and I'm not interested in what happens during that call) and I wanted that code to not be part of the transaction (by using TransactionScopeOption.Suppress
option).
So initially I made something similar to the methodFails
that I have commented in the code below. It got me a nice "System.InvalidOperationException: 'TransactionScope nested incorrectly'". I looked up in SO for somebody having similar problems, and found this Question where the answer by ZunTzu gave me the idea for method1
using TransactionScopeAsyncFlowOption.Enabled
option, which works as I expected for methodFails
but without the exception.
Then I thought of an alternative that I put in method2
that consists in putting the async code in a third method (method3
) called by firing-and-forget while the TransactionScopeOption.Suppress
option is kept in the non-async method2
. And this approach seems to work as good as method1
in my sample program.
So my question is: which approach is better, method1
or method2
, or maybe a third one that I have no thought about? I'm leaning for method1
because it sounds like "the people making the TransactionScope class put that TransactionScopeAsyncFlowOption there for a reason". But the fact that TransactionScopeAsyncFlowOption.Enabled
is not the default for a TransactionScope makes me think that maybe there is a performance hit by enabling that, and fire-and-forget may be a special case where I can save that performance hit.
The sample code:
class Program
{
static void Main(string[] args)
{
using (TransactionScope scope1 = new TransactionScope())
{
// Do some stuff in scope1...
// Start calls that could execute async code
//Task a = methodFails(); // This commented method would launch exception: System.InvalidOperationException: 'TransactionScope nested incorrectly'
Task b = method1(); // Fire and forget
method2();
// Rest of stuff in scope1 ...
}
Console.ReadLine();
}
static async Task methodFails()
{
//Start of non-transactional section
using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress))
{
//Do non-transactional work here
Console.WriteLine("Hello World 0.1!!");
await Task.Delay(10000);
Console.WriteLine("Hello World 0.2!!");
}
//Restores ambient transaction here
Console.WriteLine("Hello World 0.3!!");
}
static async Task method1()
{
//Start of non-transactional section
using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
{
//Do non-transactional work here
Console.WriteLine("Hello World 1.1!!");
await Task.Delay(10000);
Console.WriteLine("Hello World 1.2!!");
}
//Restores ambient transaction here
Console.WriteLine("Hello World 1.3!!");
}
static void method2()
{
//Start of non-transactional section
using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress))
{
//Do non-transactional work here
Task ignored = method3(); // Fire and forget
}
//Restores ambient transaction here
Console.WriteLine("Hello World 2.2!!");
}
static async Task method3()
{
//Do non-transactional work here
Console.WriteLine("Hello World 2.1!!");
await Task.Delay(10000);
Console.WriteLine("Hello World 2.3!!");
}
}