Asynchronously commit or rollback a transaction scope

C#.NetAsync AwaitTransactionscope

C# Problem Overview


As many knows, TransactionScope were forgotten when the async await pattern was introduced in .Net. They were broken if we were trying to use some await call inside a transaction scope.

Now this is fixed thanks to a scope constructor option.

But it looks to me there is still a missing piece, at least I am unable to find how to do that in a simple "transaction scope like" way: how to await the commit or rollback of a scope?

Commit and rollback are IO operations too, they should be awaitable. But since they happen on scope disposal, we have to await the dispose. That is not doable without having IAsyncDisposable implemented by transaction scopes, which is not currently the case.

I had a look at the System.Transactions.Transaction interface too: no awaitable methods there either.

I understand that committing and rollbacking is almost just sending a flag to the database, so it should be fast. But with distributed transactions, that could be less fast. And anyway, that is still some blocking IO.

About distributed cases, remember this may trigger a two phases commit. In some cases additional durable resources are enlisted during the first phase (prepare). It then usually means some additional queries are issued against those lately enlisted resources. All that happening during the commit.

So is there any way to await a transaction scope? Or a System.Transactions.Transaction instead?

Note: I do not consider this to be a duplicate of "Is it possible to commit/rollback SqlTransaction in asynchronous?". SqlTransaction are more limited than system transactions. They can address only SQL-Server and are never distributed. Some other transactions do have async methods, such as Npgsql. Now for having async methods on transaction scopes/system transaction, DbTransaction may be required to have async methods. (I do not know the internals of system transaction, but it is maybe using this ADO.NET contract. The way we enlist connection into system transaction does let me think it does not use it though.)
Update: DbTransaction does have them in .Net Core 3.0, see #35012 (notably thanks to Roji).

C# Solutions


Solution 1 - C#

There's no way to implement it so far. But they work on it

Solution 2 - C#

Maybe a late answer, but what you want basically boils down to a kind of syntactic sugar that can be easily created on your own.

Generalizing your problem, I implemented an "async using" syntax, which allows both the body and the "dispose" part of the "using" to be awaitable. Here is how it looks:

async Task DoSomething()
{ 
    await UsingAsync.Do(
        // this is the disposable usually passed to using(...)
        new TransactionScope(TransactionScopeAsyncFlowOption.Enabled), 
        // this is the body of the using() {...}
        async transaction => {
            await Task.Delay(100);   // do any async stuff here...
            transaction.Complete();  // mark transaction for Commit
        } // <-- the "dispose" part is also awaitable
    );
}

The implementation is as simple as this:

public static class UsingAsync
{
    public static async Task Do<TDisposable>(
        TDisposable disposable, 
        Func<TDisposable, Task> body)
        where TDisposable : IDisposable
    {
        try
        {
            await body(disposable);
        }
        finally
        {
            if (disposable != null)
            {
                await Task.Run(() => disposable.Dispose());
            }
        }
    }
}

There is a difference in error handling, compared to the regular using clause. When using UsingAsync.Do, any exception thrown by the body or the dispose will be wrapped inside an AggregateException. This is useful when both body and dispose each throw an exception, and both exceptions can be examined in an AggregateException. With the regular using clause, only the exception thrown by dispose will be caught, unless the body is explicitly wrapped in try..catch.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFr&#233;d&#233;ricView Question on Stackoverflow
Solution 1 - C#GlebView Answer on Stackoverflow
Solution 2 - C#felix-bView Answer on Stackoverflow