如何在可取消的 async/await 中处理 TransactionScope? [英] How to dispose TransactionScope in cancelable async/await?
问题描述
我正在尝试使用新的 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屋!