Запуск нескольких асинхронных считывателей выполняется не асинхронно

#c# #async-await #ado.net

#c# #асинхронное ожидание #ado.net

Вопрос:

В приведенном ниже коде я пытаюсь выполнить три запроса асинхронно. Когда они запускаются, это определенно асинхронно, поскольку он говорит «запустить sync1, запустить sync2, запустить sync3», и это происходит немедленно. Когда я запускаю его не асинхронно, я получаю ‘start sync1, end sync1, … как и следовало ожидать.

Но, похоже, затем выполняется ExecuteReaderAsync последовательно для каждого вызова, ожидая завершения каждого из них перед запуском следующего и занимая точно такое же время, когда я запускаю их последовательно, и ожидая одинаковое время между каждым.

Мой Sql server — enterprise 2017 на другом сервере.

Я понимаю, что асинхронность касается неблокирующих задач, но после каждого одновременного запуска я ожидал, что каждая из трех задач завершится примерно в одно и то же время. Вместо этого, когда они запускались вместе, фактические команды выполнялись, по-видимому, синхронно, одна за другой, занимая одинаковое время.

Кто-нибудь может объяснить, как приведенный ниже код на самом деле выполняется с асинхронной точки зрения и почему он не выполняет то, что я ожидал?

Даже если я изменю инструкцию execute и оберну ее в Task.Run(), чтобы поместить ее в threadpool, я получаю точно такой же ответ.

Спасибо.

 using System;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Threading.Tasks;

namespace AsyncSample
{
    internal class Tester
    {
        internal void Run()
        {
            AsyncTests();
            Console.ReadKey();
        }

        private async void AsyncTests()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            var overallStart = sw.ElapsedMilliseconds;

            Task<bool> async1 = RunASync1();
            Task<bool> async2 = RunASync2();
            Task<bool> async3 = RunASync3();

            await Task.WhenAll(async1, async2, async3);

            var overallEnd = sw.ElapsedMilliseconds - overallStart;
            Console.WriteLine($"Sync test took {Math.Round(overallEnd / 1000.0, 1)} seconds.");
        }

        private async Task<bool> RunASync1()
        {
            Console.WriteLine("   async1 start");
            await RunASyncQuery("SELECT TOP 10000 * from TABLE1");
            Console.WriteLine("   async1 end");
            return true;
        }
        private async Task<bool> RunASync2()
        {
            Console.WriteLine("   async2 start");
            await RunASyncQuery("SELECT TOP 10000 * from TABLE2");
            Console.WriteLine("   async2 end");
            return true;
        }
        private async Task<bool> RunASync3()
        {
            Console.WriteLine("   async3 start");
            await RunASyncQuery("SELECT TOP 10000 * from TABLE3");
            Console.WriteLine("   async3 end");
            return true;
        }

        private async Task<bool> RunASyncQuery(string sql)
        {
            string connectionString = "Server=MyServer;Database=MyDatabase;Integrated Security=SSPI;Asynchronous Processing=true";
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                SqlCommand command = new SqlCommand(sql, connection);
                await connection.OpenAsync();
                SqlDataReader reader = await command.ExecuteReaderAsync();
            }
            return true;
        }
    }
}
  

Комментарии:

1. async != parallelism

2. да, спасибо за ваш подробный ответ 😉 Я знаю, что асинхронность — это не параллелизм, это неблокирующий. Поэтому я предположил, что (особенно при запуске в пуле потоков) будет иметь возможность запускать все 3, учитывая, что сервер более чем способен, и он определенно запускает все 3 вместе, просто затем выполняет по 1 запросу за раз, блокируя остальные. Я просто хотел бы знать, почему это происходит.

3. Вы пытались использовать разные подключения или включить несколько активных наборов результатов (MARS) ?

4. Не ожидайте их await RunASyncQuery("SELECT TOP 10000 * from TABLE1"); Просто верните задачу.

5. Тот факт, что время асинхронного выполнения аналогично синхронному, не обязательно означает, что задачи выполняются одна за другой. Существует большая неизвестность — объем данных, которые вы извлекаете из базы данных. При относительно большом размере записи 10000 записей могут образовывать значительный объем данных, что может создать узкое место на этапе перехода (не выполнения запроса). То есть ваши запросы могут выполняться сервером одновременно и достаточно быстро, но объем данных может привести к перегрузке передачи, что, в свою очередь, может привести к увеличению времени выполнения, аналогичного синхронному выполнению.