C#MySql驱动程序-异步操作 [英] C# MySql Driver - Async Operations

查看:129
本文介绍了C#MySql驱动程序-异步操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Lately i have began working with the MySQL driver for C# https://github.com/mysql/mysql-connector-net

使用异步/等待,我尝试在并行任务中运行简单的选择查询

Working with async/await i tried to run simple select queries in parallel tasks

这基本上就是代码的样子:

This is basically how the code looks:

    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才能开始.

What i expected was for the both queries to run in parallel, what i saw was that RunQueryA() began to run and only once it was done RunQueryB could begin.

自然,这表明查询中使用的一种或多种方法正在阻止. 为了找出答案,我下载了最新的MySQL驱动程序源代码(从其github存储库中)并寻找了异步方法的实现.

Naturally it would suggest that one or more of the methods that were used in the query are blocking. To find out, i downloaded the latest MySQL driver source code (from their github repo) and looked for the implementation of the async methods.

例如,我查看了ExecuteReaderAsync的实现,它使我进入了基类System.Data.Common.DbCommand,它是BCL的一部分

I looked for instance at the implementation of ExecuteReaderAsync and it led me to the base class System.Data.Common.DbCommand which is part of the BCL

我在.NET参考源中查找了该类 https://referencesource.microsoft.com/# System.Data/System/Data/Common/DBCommand.cs,1875e74763fd9ef2

I looked up that class in the .NET Reference source https://referencesource.microsoft.com/#System.Data/System/Data/Common/DBCommand.cs,1875e74763fd9ef2

我所看到的真的让我感到困惑:

And what i saw really confused me:

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将同步运行并阻塞调用线程.

In this line, ExecuteReader would run synchronously and block the calling thread.

ExecuteReader调用抽象方法

ExecuteReader calls an abstract method

abstract protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior);

在MySQL驱动程序中被覆盖:

which is overridden inside the MySQL driver:

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

MySQL内部的实现基本上调用了ExecuteReader的同步版本...

The implementation inside the MySQL basically calls the synchronous version of ExecuteReader...

因此,简而言之,ExecuteReaderAsync()同步运行ExecuteReader()并阻塞调用线程.

So in short, ExecuteReaderAsync() runs ExecuteReader() synchronously and blocks the calling thread.

如果我弄错了,请纠正我,但事实确实如此.

Please correct me if i am mistaken but it really seems to be the case.

在BCL的DbCommand类或MySQL驱动程序实现中,我无法确切地指出是谁该引起的.

I can't exactly pinpoint who is to blame here, the DbCommand class of the BCL or the MySQL driver implementation...

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

On one hand, the MySQL driver should've taken it into account, On the other hand, since DbCommand provides a base implementation of ExecuteDbDataReaderAsync it should at least launch the synchronous version of ExecuteReader in a worker thread (let alone using actual asynchronous I/O) so it would not block.

对此有何看法?

我该怎么办? 我可以自己将ExecuteReaderAsync作为任务启动,但是我不喜欢这种解决方案.

What can i do as a work around? I could just launch ExecuteReaderAsync as a task by myself but i don't like this solution.

您有什么建议?

谢谢, 阿里克

推荐答案

DbCommand类从(至少).NET 2.0开始就存在.当Microsoft在.NET 4.5中添加ExecuteNonQueryAsyncExecuteReaderAsync等方法时,他们必须以一种向后兼容的方式来做到这一点.

The DbCommand class has been around since (at least) .NET 2.0. When Microsoft added the ExecuteNonQueryAsync, ExecuteReaderAsync etc. methods in .NET 4.5, they had to do it in a backwards-compatible way.

执行此操作的最佳方法是执行.NET框架的工作:委托给现有的同步方法,并将其返回值包装在Task中. (在实现中通过调用Task.Run来使方法异步"几乎从来不是一个好主意;有关详细说明,请参见

The best way to do this was to do what the .NET framework does: delegate to the existing synchronous method and wrap its return value in a Task. (It's almost never a good idea to make a method "asynchronous" by calling Task.Run in the implementation; for a more detailed explanation, see Should I expose asynchronous wrappers for synchronous methods? and Task.Run Etiquette and Proper Usage.)

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

To get true asynchronous behaviour, the developer of the database connection library has to convert it to being truly async. This can be difficult; making a large synchronous codebase asynchronous may involve rewriting a large portion of the code.

目前,Oracle的.NET MySQL连接器尚未实现真正的异步方法. MySQL错误70111 报告了MySQL连接器中的此问题.在此问题中进一步讨论了.

At the moment, Oracle's MySQL connector for .NET does not implement true asynchronous methods. MySQL Bug 70111 reports this problem in the MySQL connector. It's also discussed further in this question.

我建议使用我一直在使用的库: NuGet上的MySqlConnector GitHub .它是.NET和.NET Core的MySQL协议的完全独立,完全异步的实现.该API与官方的MySql.Data连接器相同,因此对于大多数项目(需要真正的异步DB连接),它应该可以直接替换.

I would recommend using a library I've been working on: MySqlConnector on NuGet and GitHub. It is a completely independent, fully async implementation of the MySQL protocol for .NET and .NET Core. The API is the same as the official MySql.Data connector, so it should be a drop-in replacement for most projects (that want true async DB connections).

这篇关于C#MySql驱动程序-异步操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆