如何在可取消的 async/await 中处理 TransactionScope? [英] How to dispose TransactionScope in cancelable async/await?

查看:20
本文介绍了如何在可取消的 async/await 中处理 TransactionScope?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用新的 async/await 功能来异步处理数据库.由于某些请求可能很长,我希望能够取消它们.我遇到的问题是 TransactionScope 显然具有线程关联性,而且似乎在取消任务时,它的 Dispose() 在错误的线程上运行.

I'm trying to use the new async/await feature to asynchronously work with a DB. As some of the requests can be lengthy, I want to be able to cancel them. The issue I'm running into is that TransactionScope apparently has a thread affinity, and it seems that when canceling the task, its Dispose() gets ran on a wrong thread.

具体来说,当调用 .TestTx() 时,我在 task.Wait () 上得到以下 AggregateException 包含 InvalidOperationException代码>:

Specifically, when calling .TestTx() I get the following AggregateException containing InvalidOperationException on task.Wait ():

"A TransactionScope must be disposed on the same thread that it was created."

代码如下:

public void TestTx () {
    var cancellation = new CancellationTokenSource ();
    var task = TestTxAsync ( cancellation.Token );
    cancellation.Cancel ();
    task.Wait ();
}

private async Task TestTxAsync ( CancellationToken cancellationToken ) {
    using ( var scope = new TransactionScope () ) {
        using ( var connection = new SqlConnection ( m_ConnectionString ) ) {
            await connection.OpenAsync ( cancellationToken );
            //using ( var command = new SqlCommand ( ... , connection ) ) {
            //  await command.ExecuteReaderAsync ();
            //  ...
            //}
        }
    }
}

更新:注释掉的部分是为了表明在连接打开后有一些事情要做 - 异步 - 使用该连接,但不需要该代码来重现问题.

UPDATED: the commented out part is to show there's something to be done — asynchronously — with the connection once it's open, but that code is not required to reproduce the issue.

推荐答案

问题源于我在控制台应用程序中对代码进行原型设计这一事实,我没有在问题中反映这一点.

The problem stems from the fact that I was prototyping the code in a console application, which I did not reflect in the question.

await 之后 async/await 继续执行代码的方式依赖于 SynchronizationContext.Current 的存在,而控制台应用程序默认没有,这意味着使用当前的 TaskScheduler 执行延续,它是一个 ThreadPool,因此它(可能?)在不同的线程上执行.

The way async/await continues to execute the code after await is dependent on the presence of SynchronizationContext.Current, and console application don't have one by default, which means the continuation gets executed using the current TaskScheduler, which is a ThreadPool, so it (potentially?) executes on a different thread.

因此,我们只需要一个 SynchronizationContext 来确保 TransactionScope 被放置在它创建的同一个线程上.WinForms 和 WPF 应用程序将默认使用它,而控制台应用程序可以使用自定义的,或者从 WPF 借用 DispatcherSynchronizationContext.

Thus one simply needs to have a SynchronizationContext that will ensure TransactionScope is disposed on the same thread it was created. WinForms and WPF applications will have it by default, while console applications can either use a custom one, or borrow DispatcherSynchronizationContext from WPF.

这里有两篇很棒的博客文章,详细解释了机制:
Await、SynchronizationContext 和控制台应用
Await、SynchronizationContext 和控制台应用程序:第 2 部分

Here are two great blog posts that explain the mechanics in detail:
Await, SynchronizationContext, and Console Apps
Await, SynchronizationContext, and Console Apps: Part 2

这篇关于如何在可取消的 async/await 中处理 TransactionScope?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆