C# MySql 驱动程序 - 异步操作

标签 c# mysql asynchronous

最近我开始使用 C# 的 MySQL 驱动程序 https://github.com/mysql/mysql-connector-net

使用 async/await 我尝试在并行任务中运行简单的选择查询

代码基本上是这样的:

    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 存储库)并寻找异步方法的实现。

例如,我查看了 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() 并阻塞调用线程。

如果我弄错了,请指正,但事实似乎确实如此。

我无法准确指出谁应该为此负责,是 BCL 的 DbCommand 类还是 MySQL 驱动程序实现......

一方面,MySQL 驱动程序应该考虑到这一点, 另一方面,由于 DbCommand 提供了 ExecuteDbDataReaderAsync 的基本实现,它至少应该在工作线程中启动 ExecuteReader 的同步版本(更不用说使用实际的异步 I/O 了),这样它就不会阻塞。

你怎么看?

作为解决方法,我可以做什么? 我可以自己将 ExecuteReaderAsync 作为任务启动,但我不喜欢这个解决方案。

你有什么建议?

谢谢, 阿里克

最佳答案

DbCommand 类(至少)从 .NET 2.0 开始就存在了。当 Microsoft 在 .NET 4.5 中添加 ExecuteNonQueryAsyncExecuteReaderAsync 等方法时,他们必须以向后兼容的方式进行。

最好的方法是执行 .NET 框架所做的事情:委托(delegate)给现有的同步方法并将其返回值包装在 Task 中。 (通过在实现中调用 Task.Run 使方法“异步”几乎不是一个好主意;有关更详细的解释,请参阅 Should I expose asynchronous wrappers for synchronous methods?Task.Run Etiquette and Proper Usage。)

要获得真正的异步行为,数据库连接库的开发人员必须将其转换为真正的异步行为。这可能很困难;使大型同步代码库异步可能涉及重写大部分代码。

目前,Oracle 的 .NET MySQL 连接器没有实现真正的异步方法。 MySQL Bug 70111在 MySQL 连接器中报告此问题。还在 this question 中进一步讨论了它.

我建议使用我一直在研究的库:MySqlConnector on NuGetGitHub .它是 .NET 和 .NET Core 的 MySQL 协议(protocol)的完全独立、完全异步的实现。 API 与官方 MySql.Data 连接器相同,因此它应该是大多数项目(需要真正的异步数据库连接)的直接替代品。

关于C# MySql 驱动程序 - 异步操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39967208/

相关文章:

c# - 在表中插入值的 asp.net 查询

MySQL > 表不存在。但它确实(或应该)

mysql - 使用存储过程时,where 子句中的未知列 'template'

php - 在 MySQL 中全文搜索多列?

javascript - 在调用它的函数的回调中使用参数

c# - 从异步方法获取结果

c# - 任务状态一直等待激活

c# - 使用 C# 代码时查看控件列表?

c# - 如何在启动时删除 App.xaml ResourceDictionary?

c# - NHibernate - 根据子属性过滤掉结果