@ Игорь Нет, точно такое же поведение с
ыполнении вызовов к SQL Server, которые на самом деле делают вещи, которые требуют времени,SqlDataReader.ReadAsync()
работает синхронно для меня. Есть ли способ заставить его работать асинхронно или это мой единственный вариант вызвать его вTask.Run()
?
Вот репродукция. Он использует winforms, чтобы продемонстрировать, что вызов блокирует поток 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
// <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
{
// 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<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]
(далее обрезан для краткости).
ЦелыйReadSyncOverAsync
материал выглядит особенно подозрительно для меня. Как будто SqlClient предполагает, что синхронное чтение не будет блокироваться, как будто он не знает, как использовать неблокирующий ввод-вывод или что-то в этом роде. Тем не менее, при просмотре эталонного источника или декомпиляции с помощью JustDecompile, похоже, что должна быть асинхронная поддержка, но просто почему-то эвристически / откатно решил ее не использовать.
Итак, как мне получить *Async()
вещи в SqlClient на самом деле быть асинхронными? Я думал, что эти методы должны были позволить мне писать программы без графического интерфейса без необходимости использованияTask.Run()
потому что упаковка синхронных вещей вTask.Run()
просто делать их асинхронными - бессмысленные накладные расходы ...?
Я использую .net-4.7.02542.
Я предполагаю, что это ошибка .net и отправилподключиться # 3139210 (РЕДАКТИРОВАТЬ: подключение не работает, у меня есть проект вhttps://github.com/binki/connect3139210).
ОБНОВИТЬ: Microsoft признает ошибку и исправит ее в .net-4.7.3.Я использовал случай «Технической поддержки» из подписки VS, чтобы сообщить об ошибке и получить эту информацию.