В последнем время я начал работать с драйвером 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
Я посмотрел этот класс в .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 как задачу самостоятельно, но мне не нравится это решение.
Что вы предлагаете?
Спасибо, Арик