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.
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."
Here’s the code:
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.
The way async/await continues to execute the code after
awaitis dependent on the presence ofSynchronizationContext.Current, and console application don’t have one by default, which means the continuation gets executed using the currentTaskScheduler, which is aThreadPool, so it (potentially?) executes on a different thread.Thus one simply needs to have a
SynchronizationContextthat will ensureTransactionScopeis 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 borrowDispatcherSynchronizationContextfrom WPF.Here are two great blog posts that explain the mechanics in detail:
Await, SynchronizationContext, and Console Apps
Await, SynchronizationContext, and Console Apps: Part 2