Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

180
Views
Asynchronously commit or rollback a transaction scope

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).

over 4 years ago · Santiago Trujillo
2 answers
Answer question

0

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

over 4 years ago · Santiago Trujillo Report

0

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.

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!