2016-10-10 3 views
1

В последнем время я начал работать с драйвером MySQL для C# https://github.com/mysql/mysql-connector-netC# MySql Драйвер - Async Операция

Работы с асинхронномом/ждать я пытался запускать простые запросы на выборку в параллельных задачах

Это в основном, как код выглядит:

private async Task<List<string>> RunQueryA() 
    { 
     List<string> lst = new List<string>(); 

     using (MySqlConnection conn = new MySqlConnection(someConnectionString)) 
     using (MySqlCommand cmd = conn.CreateCommand()) 
     { 
      await conn.OpenAsync(); 
      cmd.CommandText = "select someField from someTable ..."; 

      using (var reader = await cmd.ExecuteReaderAsync()) 
      { 
       // ... 
      } 
     } 

     return lst; 
    } 

    private async Task<List<string>> RunQueryB() 
    { 
     List<string> lst = new List<string>(); 

     using (MySqlConnection conn = new MySqlConnection(someConnectionString)) 
     using (MySqlCommand cmd = conn.CreateCommand()) 
     { 
      await conn.OpenAsync(); 
      cmd.CommandText = "select someField2 from someTable2 ..."; 

      using (var reader = await cmd.ExecuteReaderAsync()) 
      { 
       // ... 
      } 
     } 

     return lst; 
    } 

    public async Task Run() 
    { 
     await Task.WhenAll(RunQueryA(), RunQueryB()); 
    } 

что я ожидал был для обоих запросов работать параллельно, то, что я видел, что RunQueryA() начал работать и только один раз это было сделано RunQueryB может начаться.

Естественно, это предполагает, что один или несколько методов, которые были использованы в запросе, блокируют. Чтобы узнать, я загрузил последний исходный код драйвера MySQL (из своего репозитория github) и искал реализацию методов async.

Я смотрел, например, при осуществлении ExecuteReaderAsync и это привело меня к базовому классу System.Data.Common.DbCommand, который является частью BCL

enter image description here

Я посмотрел этот класс в .NET Ссылка источник https://referencesource.microsoft.com/#System.Data/System/Data/Common/DBCommand.cs,1875e74763fd9ef2

И то, что я видел действительно смутило меня:

public Task<DbDataReader> ExecuteReaderAsync() { 
      return ExecuteReaderAsync(CommandBehavior.Default, CancellationToken.None); 
     } 

     public Task<DbDataReader> ExecuteReaderAsync(CancellationToken cancellationToken) { 
      return ExecuteReaderAsync(CommandBehavior.Default, cancellationToken); 
     } 

     public Task<DbDataReader> ExecuteReaderAsync(CommandBehavior behavior) { 
      return ExecuteReaderAsync(behavior, CancellationToken.None); 
     } 

     public Task<DbDataReader> ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) { 
      return ExecuteDbDataReaderAsync(behavior, cancellationToken); 
     } 

     protected virtual Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) { 
      if (cancellationToken.IsCancellationRequested) { 
       return ADP.CreatedTaskWithCancellation<DbDataReader>(); 
      } 
      else { 
       CancellationTokenRegistration registration = new CancellationTokenRegistration(); 
       if (cancellationToken.CanBeCanceled) { 
        registration = cancellationToken.Register(CancelIgnoreFailure); 
       } 

       try { 
        return Task.FromResult<DbDataReader>(ExecuteReader(behavior)); 
       } 
       catch (Exception e) { 
        registration.Dispose(); 
        return ADP.CreatedTaskWithException<DbDataReader>(e); 
       } 
      } 
     } 

Это все сводится к этой линии:

return Task.FromResult<DbDataReader>(ExecuteReader(behavior)); 

В этой строке ExecuteReader будет работать синхронно и блокировать вызывающий поток.

ExecuteReader вызывает абстрактный метод

abstract protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior); 

, который перекрывается в драйвере MySQL:

protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) 
    { 
     return ExecuteReader(behavior); 
    } 

Реализация внутри MySQL в основном вызывает синхронную версию ExecuteReader ...

Короче говоря, ExecuteReaderAsync() запускает ExecuteReader() синхронно и блокирует вызывающий поток.

Пожалуйста, исправьте меня, если я ошибаюсь, но это действительно так.

Я не могу точно определить, кто виноват здесь, класс DbCommand в BCL или реализации драйвера MySQL ...

С одной стороны, драйвер MySQL должен был принимать это во внимание, С другой стороны, поскольку DbCommand обеспечивает базовую реализацию ExecuteDbDataReaderAsync, он должен хотя бы запустить синхронную версию ExecuteReader в рабочем потоке (не говоря уже об использовании фактического асинхронного ввода-вывода), чтобы он не блокировал.

Что вы думаете об этом?

Что я могу сделать для работы? Я мог бы просто запустить ExecuteReaderAsync как задачу самостоятельно, но мне не нравится это решение.

Что вы предлагаете?

Спасибо, Арик

ответ

3

DbCommand класс был вокруг, так как (по крайней мере) .NET 2.0. Когда Microsoft добавила методы ExecuteNonQueryAsync, ExecuteReaderAsync и т. Д. В .NET 4.5, они должны были сделать это обратным образом.

Лучший способ сделать это - сделать то, что делает платформа .NET: делегировать существующий синхронный метод и обернуть его возвращаемое значение в Task. (Это почти никогда не является хорошей идеей, чтобы сделать метод «асинхронным», вызвав Task.Run в реализации, для более подробного объяснения см Should I expose asynchronous wrappers for synchronous methods? и Task.Run Etiquette and Proper Usage.)

Чтобы получить истинное асинхронное поведение, разработчик библиотеки подключений к базе данных должен преобразовать его в истинно асинхронный. Это может быть сложно; создание большой синхронной асинхронной кодовой базы может включать переписывание большой части кода.

В настоящее время соединитель MySQL Oracle для .NET не реализует истинные асинхронные методы. MySQL Bug 70111 сообщает об этой проблеме в соединителе MySQL. Это также обсуждается далее в this question.

Я бы порекомендовал использовать библиотеку, в которой я работал: MySqlConnector on NuGet и GitHub. Это полностью независимая, полностью асинхронная реализация протокола MySQL для .NET и .NET Core. API такой же, как и официальный разъем MySql.Data, поэтому он должен быть заменой для большинства проектов (которые хотят иметь истинные асинхронные соединения БД).

Смежные вопросы