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

时间:2016-10-10 21:43:23

标签: 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 repo),并寻找异步方法的实现。

我在ExecuteReaderAsync的实现中寻找实例,它引导我进入基类System.Data.Common.DbCommand,它是BCL的一部分

enter image description here

我在.NET Reference源代码中查找了该类 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作为任务,但我不喜欢这个解决方案。

你有什么建议?

谢谢, 阿里克

2 个答案:

答案 0 :(得分:6)

DbCommand类自(至少).NET 2.0以来就存在。当Microsoft在.NET 4.5中添加ExecuteNonQueryAsyncExecuteReaderAsync等方法时,他们必须以向后兼容的方式执行此操作。

执行此操作的最佳方法是执行.NET框架的操作:委派现有的同步方法并将其返回值包装在Task中。 (通过在实现中调用Task.Run来制作方法&#34;异步&#34;几乎不是一个好主意;有关更详细的说明,请参阅Should I expose asynchronous wrappers for synchronous methods?和{{3 }}。)

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

目前,Oracle用于.NET的MySQL连接器并未实现真正的异步方法。 Task.Run Etiquette and Proper Usage在MySQL连接器中报告此问题。它还在MySQL Bug 70111进一步讨论。

我建议您使用我正在处理的图书馆:this questionMySqlConnector on NuGet。它是.NET和.NET Core的MySQL协议的完全独立,完全异步的实现。 API与官方MySql.Data连接器相同,因此它应该是大多数项目(需要真正的异步数据库连接)的替代品。

答案 1 :(得分:0)

我最近遇到了同样的问题,并使用StringEscapeUtils.unescapeHtml(myCode);MySqlHelper和其他类似方法在MySql.Data.MySqlClient中发现了ExecuteReaderAsync类。