我在服务器中有一个数据库,看起来async方法不起作用。
这是我的代码:
static async void Example()
{
string connectionString =
"Server=mydomainname.com;" +
"Port=3306;" +
"Database=scratch;" +
"Uid=Assassinbeast;" +
"Password=mypass123;" +
"AllowUserVariables= true;";
MySql.Data.MySqlClient.MySqlConnection sqConnection = new MySql.Data.MySqlClient.MySqlConnection(connectionString);
await sqConnection.OpenAsync();
Console.Write("Opened. Now Click to close");
Console.ReadLine();
sqConnection.Close();
}
static void Main(string[] args)
{
Console.ReadLine();
Example();
Console.WriteLine("Done");
Console.ReadLine();
}
等待" await"声明,它实际上应该跳回Main()函数并写出" Done"。但它不这样做。它只是同步运行,就像它不是一个异步方法,它将首先编写"完成"一旦功能完全完成。
那么我做错了什么?这是一个错误吗?
更新
好的,所以在我得到一些答案之后,我实际上仍然看不到OpenAsync()和Open()之间的任何区别。
我开始尝试测试更多的东西,我想我可以得出结论,异步方法不工作
这是我的新代码:
static async Task Example()
{
string connectionString =
"Server=mydomainname.com;" +
"Port=3306;" +
"Database=scratch;" +
"Uid=Assassinbeast;" +
"Password=mypass123;" +
"AllowUserVariables= true;";
using (var sqConnection = new MySql.Data.MySqlClient.MySqlConnection(connectionString))
{
Console.WriteLine("Opening");
await sqConnection.OpenAsync();
Console.WriteLine("Opened. Now Closing");
}
}
static async Task Example2()
{
//Lets pretend this is a database that my computer will try to connect to
Console.WriteLine("Opening");
await Task.Delay(1000); //Lets say it takes 1 second to open
Console.WriteLine("Opened. Now Closing");
}
static void Main(string[] args)
{
Console.ReadLine();
Task.Run(() => Example());
Task.Run(() => Example());
Task.Run(() => Example());
Task.Run(() => Example());
Task.Run(() => Example());
Task.Run(() => Console.WriteLine("Done"));
Console.ReadLine();
}
这里当我运行Example()5次时,它将输出如下:
它需要最多3秒才会写出"完成"。 请注意,我的计算机根本不在CPU上工作,因为它只是等待并连接到数据库,大约需要1秒才能连接到。
所以它实际上阻止了我的计算机线程并且没有运行多线程,否则它会写出" Done" immediedtly。
所以,如果我调用Example2()而不是Example(),那么我得到的结果就是我想要的和我期望的结果:
在这里,它是真正的异步方法,因为我可以在我的计算机上一次完成6件事,只有2个核心。 但是第一个例子,我一次只能做两件事,因为MySQL异步方法不起作用。
我还使用sqConnection.Open()对其进行了测试,该结果与sqConnection.OpenAsync()
具有完全相同的结果所以现在,我只是想弄清楚如何同时连接数据库5次。
答案 0 :(得分:7)
从一些旧代码(6.7.2)来看,似乎mysql ADO.NET提供程序没有正确实现任何异步功能。这包括TAP模式和旧样式Begin ...,End ...异步模式。在该版本中,Db *异步方法似乎根本没有写入;他们将使用.NET中的基类,它们是同步的,看起来像:
public virtual Task<int> ExecuteNonQueryAsync(...) {
return Task.FromResult(ExecuteNonQuery(...));
}
(与在任务中包装它的额外开销100%同步; reference source here)
如果正确编写了Begin和End版本(它们不是),它可以实现如下:
public override Task<int> ExecuteNonQueryAsync(...) {
return Task<int>.Factory.FromAsync(BeginExecuteNonQueryAsync, EndExecuteNonQueryAsync, null);
}
(reference source for that method for SqlCommand)
执行此操作取决于底层套接字的某种回调api最终处理的模式,其中调用者通过套接字发送一些字节,然后注册的方法在准备就绪时从底层网络堆栈回调。
但是,mysql连接器不会这样做(它首先不会覆盖该方法;但如果确实如此,相关的开始和结束方法在某些底层套接字api上不是异步的)。 mysql连接器instead的作用是构建当前连接实例上的内部方法的委托,并在单独的线程上同步调用它。例如,你不能在同一个连接上执行第二个命令,例如:
private static void Main() {
var sw = new Stopwatch();
sw.Start();
Task.WaitAll(
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync(),
GetDelayCommand().ExecuteNonQueryAsync());
sw.Stop();
Console.WriteLine(sw.Elapsed.Seconds);
}
private static DbCommand GetDelayCommand() {
var connection = new MySqlConnection (...);
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = "SLEEP(5)";
cmd.CommandType = CommandType.Text;
return cmd;
}
(假设您是连接池并且任务数量超过最大池大小;如果异步工作,则此代码将获得一个数字,具体取决于池中的连接数而不是数字,具体取决于它和可并发运行的线程数)
这是因为代码有一个lock on the driver(管理网络内部的实际内容; *)。如果它没有(并且内部是其他线程安全的,并且其他一些方式用于管理连接池)it goes on to perform blocking calls on the underlying network stream。
所以是的,这个代码库没有异步支持。如果有人能指出我的代码,我可以看一个更新的驱动程序,但我怀疑基于内部NetworkStream
的对象看起来并没有显着不同,异步代码也没有太大的不同。 async
支持驱动程序将编写大部分内部函数,这取决于执行它的异步方式,并具有同步代码的同步包装器;或者它看起来更像SqlClient
参考源,并依赖于一些Task
包装库来抽象出同步或异步运行之间的差异。
*锁定驱动程序并不意味着它不可能使用非阻塞IO,只是该方法无法使用lock语句编写并使用非阻塞开始/结束{{1}可以在TAP模式之前编写的代码。
编辑:下载6.9.8;怀疑没有正常的异步代码(非阻塞IO操作);这里有一个错误:https://bugs.mysql.com/bug.php?id=70111
2016年7月6日更新:关于GitHub的有趣项目,可能最终在https://github.com/mysql-net/MySqlConnector解决这个问题(可能会使用更多与其成功有关的贡献者[我不再使用MySql进行任何工作]。) / p>
答案 1 :(得分:2)
嗯,可能是已经开放的汇集连接。在这种情况下,它将同步返回给您。
要注意的是await
不一定将控制权返回给调用方法。如果等待任务的状态为TaskStatus.Running
,它只返回调用方法。如果任务已完成,则执行正常。
尝试等待此方法,该方法返回状态为RanToCompletion
的任务:
public Task<int> SampleMethod()
{
return Task.FromResult(0);
}
答案 2 :(得分:1)
避免在异步方法中使用void
作为返回类型。 Async void仅适用于事件处理程序。所有其他异步方法都应返回Task
或Task<T>
。试试这个:
static async Task Example()
{
string connectionString =
"Server=mydomainname.com;" +
"Port=3306;" +
"Database=scratch;" +
"Uid=Assassinbeast;" +
"Password=mypass123;" +
"AllowUserVariables= true;";
MySql.Data.MySqlClient.MySqlConnection sqConnection = new MySql.Data.MySqlClient.MySqlConnection(connectionString);
await sqConnection.OpenAsync();
Console.Write("Opened. Now Click to close");
Console.ReadLine();
sqConnection.Close();
}
static void Main(string[] args)
{
Console.ReadLine();
Task.Run(() => Example()).Wait();
Console.WriteLine("Done");
Console.ReadLine();
}
<强>更新强>
正如dcastro所解释的那样,这就是async-await的工作原理:
如果等待的任务尚未完成且仍在运行,Example()
将返回其调用方法,因此主线程不会被阻止。当任务完成后,来自ThreadPool的线程(可以是任何线程)将返回其先前状态的Example()
并继续执行。
或者第二种情况是任务已经完成执行并且结果可用。当到达等待的任务时,编译器知道它具有结果并将继续在同一个线程上执行代码。