DbCommand.ExecuteReaderAsync throws TaskCanceledException with wrong CancellationToken #1250
Description
I am witnessing this behavior in packages\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll when running from a .Net Core 3.1 application.
When I call DbCommand.ExecuteReaderAsync with a CancellationToken and cancel it before it finishes, an exception is thrown that contains a TaskCanceledException buried down in the InnerExceptions. I expect that behavior, but the issue is that the TaskCanceledException.CancellationToken is not the CancellationToken that I provided to ExecuteReaderAsync. I do not know where this other CancellationToken is coming from, but I thought we were only supposed to suppress TaskCanceledException in our applications if it is for the provided CancellationToken, so I end up with a lot more unhandled exceptions than I want.
Is it safe to suppress any TaskCanceledException, no matter what CancellationToken it contains? Where does this mystery CancellationToken come from, and could ExecuteReaderAsync just return the CancellationToken given to it?
using (var cts = new CancellationTokenSource())
{
using (var cmd = db.GetSqlStringCommand(queryString))
{
cts.Cancel();
try
{
using (var rdr = cmd.ExecuteReaderAsync(cts.Token).Result)
{
}
}
catch (Exception ex3)
{
// if you inspect ex3, you will find a TaskCanceledException buried in InnerExceptions, but its CancellationToken is not cts.Token
throw;
}
}
}
Additional Comments:
I have tested this with "System.Data.SqlClient.SqlCommand" and "Microsoft.Data.SqlClient.SqlCommand". The result is the same in both cases where the TaskCanceledException holds a new CancellationToken and the TaskCanceledException is wrapped in an AggregateException, so I have to dig down in the exception stack to find the TaskCanceledException , which means I cannot just catch TaskCanceledException.
System.AggregateException: One or more errors occurred. (A task was canceled.)
---> System.Threading.Tasks.TaskCanceledException: A task was canceled.
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task1.GetResultCore(Boolean waitCompletionNotification) at System.Threading.Tasks.Task
1.get_Result()