如何使SqlDataReader.ReadAsync()异步运行?

当调用SQL Server实际执行需要花费时间的事情时, SqlDataReader.ReadAsync()为我同步运行。 有没有办法强制它以异步方式运行,或者是我在Task.Run()调用它的唯一选择?

这是一个复制品。 它使用winforms来certificate该调用阻止了GUI线程。 请注意,T-SQL必须实际执行某些操作 – 使用WAITFOR DELAY '00:00:20'无法重现

 using System; using System.Configuration; using System.Data.Common; using System.Data.SqlClient; using System.Threading.Tasks; using System.Windows.Forms; static class SqlDataReaderReadAsyncProgram { static async void Form_Shown(object sender, EventArgs e) { var form = (Form)sender; // Declare your connection string in app.config like //  using (DbConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings[0].ConnectionString)) { form.Text = "connecting…"; await connection.OpenAsync(); form.Text = "connected!"; // Install a stored procedure. using (var command = connection.CreateCommand()) { command.CommandText = "SET NOCOUNT ON" + " SELECT 'a'" + " DECLARE @t DATETIME = SYSDATETIME()" + " WHILE DATEDIFF(s, @t, SYSDATETIME()) < 20 BEGIN" + " SELECT 2 x INTO #y" + " DROP TABLE #y" + " END" + " SELECT 'b'"; form.Text = "executing…"; using (var reader = await command.ExecuteReaderAsync()) { form.Text = "reading…"; do { // Blocks on the second call until the second resultset is returned by SQL Server while (await reader.ReadAsync()) { } } while (await reader.NextResultAsync()); form.Text = "done!"; } } } await Task.Delay(TimeSpan.FromSeconds(5)); form.Close(); } [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var form = new Form(); form.Shown += Form_Shown; Application.Run(form); } } 

当我运行它时,窗口变为“(无响应)”20秒,然后报告它完成(请注意,在VS中调试时,“(无响应)”文本不会出现,但它仍会冻结相同的内容)。 如果我在VS中进行调试并在冻结时将其分解,我会看到它与一个看起来像这样的调用堆栈:

  [Managed to Native Transition] System.Data.dll!SNINativeMethodWrapper.SNIReadSyncOverAsync(System.Runtime.InteropServices.SafeHandle pConn, ref System.IntPtr packet, int timeout) Unknown System.Data.dll!System.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() Unknown System.Data.dll!System.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket() Unknown System.Data.dll!System.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer() Unknown System.Data.dll!System.Data.SqlClient.TdsParserStateObject.TryReadByteArray(byte[] buff, int offset, int len, out int totalRead) Unknown System.Data.dll!System.Data.SqlClient.TdsParserStateObject.TryReadInt64(out long value) Unknown System.Data.dll!System.Data.SqlClient.TdsParser.TryProcessDone(System.Data.SqlClient.SqlCommand cmd, System.Data.SqlClient.SqlDataReader reader, ref System.Data.SqlClient.RunBehavior run, System.Data.SqlClient.TdsParserStateObject stateObj) Unknown System.Data.dll!System.Data.SqlClient.TdsParser.TryRun(System.Data.SqlClient.RunBehavior runBehavior, System.Data.SqlClient.SqlCommand cmdHandler, System.Data.SqlClient.SqlDataReader dataStream, System.Data.SqlClient.BulkCopySimpleResultSet bulkCopyHandler, System.Data.SqlClient.TdsParserStateObject stateObj, out bool dataReady) Unknown System.Data.dll!System.Data.SqlClient.SqlDataReader.TryHasMoreRows(out bool moreRows) Unknown System.Data.dll!System.Data.SqlClient.SqlDataReader.TryReadInternal(bool setTimeout, out bool more) Unknown System.Data.dll!System.Data.SqlClient.SqlDataReader.ReadAsync.AnonymousMethod__0(System.Threading.Tasks.Task t) Unknown System.Data.dll!System.Data.SqlClient.SqlDataReader.InvokeRetryable(System.Func<System.Threading.Tasks.Task, System.Threading.Tasks.Task> moreFunc, System.Threading.Tasks.TaskCompletionSource source, System.IDisposable objectToDispose) Unknown System.Data.dll!System.Data.SqlClient.SqlDataReader.ReadAsync(System.Threading.CancellationToken cancellationToken) Unknown System.Data.dll!System.Data.Common.DbDataReader.ReadAsync() Unknown > SqlDataReaderReadAsync.exe!SqlDataReaderReadAsyncProgram.Form_Shown(object sender, System.EventArgs e) Line 36 C# [Resuming Async Method] 

(为简洁起见,进一步修剪)。

整个ReadSyncOverAsync东西对我来说特别可疑。 这就像SqlClient假设同步读取不会阻塞,好像它不知道如何使用非阻塞IO或其他东西。 然而,当查看参考源或使用JustDecompile进行反编译时,看起来应该是异步支持,但它只是在某种程度上启发式/后退决定不使用它。

那么,如何让SqlClient中的* Async()内容实际上是异步的呢? 我认为这些方法应该使我能够编写无线响应式GUI程序而无需使用Task.Run()因为在Task.Run()包装同步事物只是为了使它们异步是没有意义的开销……?

我正在使用.net-4.7.02542。

我假设这是一个.net错误并已提交连接#3139210 。

更新 :Microsoft承认该错误并将其修复为.net-4.7.3。 我使用VS订阅中的“技术支持”案例来报告错误并获取此信息。