Performance tests showed almost 2x performance benefit to a using a dedicated synchronous data-reading thread, compared to using the Begin or Async APIs with their thread pool continuations. (Since tens of millions of rows will be loaded in as many seconds, we prefer performance in this case.)
An extension method for convenient token passing:
public static SqlDataReader ExecuteReader(this SqlCommand command, CommandBehavior commandBehavior, CancellationToken cancellationToken)
{
try
{
using (cancellationToken.Register(command.Cancel))
return command.ExecuteReader(commandBehavior);
}
catch (SqlException) when (cancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException(cancellationToken);
}
}
I did some spelunking with Reflector decompilation. The Begin and Async versions are both very frugal, but are both fully based on the TPL async. Because of that, there is thread pool dispatching for continuations on both.
This extension method has no threading overhead. The thread that calls Cancel
on the token source will also call command.Cancel
which will immediately cause a SqlException
in the data thread.