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 .