c# ado.net task-parallel-library sqlclient

c# - ¿Cómo puedo hacer que SqlCommand.ExecuteReaderAsync() y SqlDataReader.ReadAsync() se ejecuten de forma asíncrona?



ado.net task-parallel-library (0)

Cuando realizo llamadas a SQL Server que realmente hacen cosas que llevan tiempo, SqlCommand.ExecuteReaderAsync() y SqlDataReader.ReadAsync() ejecutan de forma sincronizada para mí. ¿Hay alguna forma de obligarlos a ejecutar de forma asíncrona o es mi única opción llamarlos en Task.Run() ?

Aquí hay una repro. Utiliza winforms para demostrar que la llamada bloquea el hilo de la GUI. Tenga en cuenta que el T-SQL tiene que hacer algo, esto no es reproducible con 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 // <connectionStrings><remove name="LocalSqlServer"/><add name="LocalSqlServer" connectionString="Data Source=localhost/SQLEXPRESS;Integrated Security=true"/></connectionStrings> 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 { 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); } }

Cuando ejecuto esto, la ventana se convierte en "(No responde)" durante 20 segundos antes de informar que está hecho (tenga en cuenta que al depurar en VS, el texto "(No responde)" no aparece pero aún se congela). Si depuro en VS y lo rompo mientras está congelado, lo veo sentado con una pila de llamadas que se ve así:

[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<bool>(System.Func<System.Threading.Tasks.Task, System.Threading.Tasks.Task<bool>> moreFunc, System.Threading.Tasks.TaskCompletionSource<bool> 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]

(recortado adicionalmente por brevedad).

Todo el material de ReadSyncOverAsync parece especialmente sospechoso. Es como si SqlClient asumiera que una lectura síncrona no se bloqueará, como si no supiera cómo usar IO sin bloqueo o algo así. Sin embargo, al ver la fuente de referencia o descompilar con JustDecompile, parece que se supone que hay soporte asincrónico, pero de alguna manera heurística / fallidamente decidió no usarlo.

Entonces, ¿cómo consigo que las cosas * Async() en SqlClient sean realmente asíncronas? Pensé que se suponía que estos métodos me permitirían escribir programas de GUI sin hilos sin necesidad de usar Task.Run() porque envolver cosas sincrónicas en Task.Run() solo para hacerlas asincrónicas es una sobrecarga sin sentido ...?

Estoy usando .net-4.7.02542.

Asumo que esto es un error .net y he enviado connect # 3139210 .