are*_*ler 2 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());
}
Run Code Online (Sandbox Code Playgroud)
我期望两个查询并行运行,我看到RunQueryA()开始运行,只有一次完成RunQueryB可以开始.
当然,它会建议查询中使用的一个或多个方法是阻塞的.为了找到答案,我下载了最新的MySQL驱动程序源代码(来自他们的github repo),并寻找异步方法的实现.
我在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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这一切归结为这一行:
return Task.FromResult<DbDataReader>(ExecuteReader(behavior));
Run Code Online (Sandbox Code Playgroud)
在这一行中,ExecuteReader将同步运行并阻塞调用线程.
ExecuteReader调用抽象方法
abstract protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior);
Run Code Online (Sandbox Code Playgroud)
在MySQL驱动程序中重写:
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
{
return ExecuteReader(behavior);
}
Run Code Online (Sandbox Code Playgroud)
MySQL内部的实现基本上调用了ExecuteReader的同步版本......
简而言之,ExecuteReaderAsync()同步运行ExecuteReader()并阻塞调用线程.
如果我弄错了请纠正我,但事实确实如此.
我不能确切地指出谁应该责备这里,BCL的DbCommand类或MySQL驱动程序实现......
一方面,MySQL驱动程序应该考虑到它,另一方面,由于DbCommand提供了ExecuteDbDataReaderAsync的基本实现,它至少应该在工作线程中启动同步版本的ExecuteReader(更不用说使用实际的异步I/O)所以它不会阻止.
怎么想呢?
我可以做些什么来解决这个问题?我可以自己启动ExecuteReaderAsync作为任务,但我不喜欢这个解决方案.
你有什么建议?
谢谢,Arik
的DbCommand
自(至少)NET 2.0类已经存在了.当微软添加ExecuteNonQueryAsync
,ExecuteReaderAsync
方法等在.NET 4.5,他们不得不做一个向后兼容的方式.
执行此操作的最佳方法是执行.NET框架所执行的操作:委派现有的同步方法并将其返回值包装在Task
.(通过调用Task.Run
实现来使方法"异步"几乎绝不是一个好主意;有关更详细的解释,请参阅我是否应该为同步方法公开异步包装器?以及Task.Run Etiquette和Proper Usage.)
要获得真正的异步行为,数据库连接库的开发人员必须将其转换为真正的异步.这可能很困难; 使大型同步代码库异步可能涉及重写大部分代码.
目前,Oracle用于.NET的MySQL连接器并未实现真正的异步方法.MySQL Bug 70111在MySQL连接器中报告此问题.在这个问题中也进一步讨论了它.
我建议使用我一直在使用的库:NuGet和GitHub上的MySqlConnector.它是.NET和.NET Core的MySQL协议的完全独立,完全异步实现.API与官方MySql.Data
连接器相同,因此它应该是大多数项目(需要真正的异步数据库连接)的直接替代品.