异步提交或回滚事务范围

Asynchronously commit or rollback a transaction scope

正如许多人所知,当 async await 模式被引入 .Net 时,TransactionScope 被遗忘了。如果我们试图在事务范围内使用一些 await 调用,它们就会被破坏。

现在由于 scope constructor option.

解决了这个问题

但在我看来仍然缺少一块,至少我无法找到如何以简单的 "transaction scope like" 方式做到这一点:如何等待范围的提交或回滚?

提交和回滚也是IO操作,应该是可以等待的。但由于它们发生在范围处置上,我们必须等待处置。如果没有 IAsyncDisposable 由事务范围实现,这是不可行的,目前情况并非如此。

我也查看了 System.Transactions.Transaction 接口:那里也没有可等待的方法。

我明白提交和回滚几乎只是向数据库发送一个标志,所以应该很快。但是对于分布式事务,速度可能会慢一些。无论如何,这仍然是一些阻塞 IO。

关于分布式案例,请记住这可能会触发两阶段提交。在某些情况下,在第一阶段(准备)征用额外的持久资源。这通常意味着针对那些最近征用的资源发出了一些额外的查询。提交期间发生的所有事情。

那么有什么方法可以等待事务作用域吗?或者 System.Transactions.Transaction 代替?

注意: 我不认为这是“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 可能需要具有异步方法的副本。(我不知道系统的内部结构交易,但它可能正在使用这个 ADO.NET 合同。我们将连接加入系统交易的方式确实让我认为它没有使用它。)
更新:DbTransaction 在 .Net Core 3.0 中确实有它们,请参阅 #35012 (notably thanks to Roji)。

也许是一个迟到的答案,但你想要的基本上归结为一种可以轻松自行创建的语法糖。

概括你的问题,我实现了一个 "async using" 语法,它允许正文和 "using" 的 "dispose" 部分都可以等待。外观如下:

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
    );
}

实现就这么简单:

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());
            }
        }
    }
}

与常规 using 子句相比,错误处理有所不同。使用 UsingAsync.Do 时,正文或处置抛出的任何异常都将包含在 AggregateException 中。这在 body 和 dispose 都抛出异常时很有用,并且两个异常都可以在 AggregateException 中检查。使用常规 using 子句,只会捕获 dispose 抛出的异常,除非正文明确包含在 try..catch 中。

目前还没有办法实现。但是they work on it