尝试使用async / await和transactions将多个记录插入MySQL数据库,但是它仍然会导致UI在循环中变得冻结/无响应。
查看以下代码,我做错了什么或如何实现这一点,以便UI在此过程中仍然响应。
异步方法
public static async Task AddRecords() {
foreach ( string month in Months ) {
await MakeTable( month );
string query="INSERT INTO `"+month+"` ( Caller, Started, Dialed, DurationSec, DurationMin, Cost, Location, Switch ) VALUES (@Caller, @Started, @Dialed, @DurationSec, @DurationMin, @Cost, @Location, @Switch);";
using ( MySqlConnection cn=new MySqlConnection( ConnectionString.ToString() ) ) {
await cn.OpenAsync();
using ( MySqlTransaction trans=cn.BeginTransaction() ) {
using ( MySqlCommand cmd=new MySqlCommand( query, cn, trans ) ) {
cmd.CommandType=CommandType.Text;
foreach ( Record r in CDR.Records ) {
if ( r.Started.ToString( "yyyy-MM" )==month ) {
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue( "@Caller", r.Caller );
cmd.Parameters.AddWithValue( "@Started", r.Started );
cmd.Parameters.AddWithValue( "@Dialed", r.Dialed );
cmd.Parameters.AddWithValue( "@DurationSec", r.Duration );
cmd.Parameters.AddWithValue( "@DurationMin", Math.Ceiling( r.Duration/60 ) );
cmd.Parameters.AddWithValue( "@Cost", r.Cost );
cmd.Parameters.AddWithValue( "@Location", r.Location );
cmd.Parameters.AddWithValue( "@Switch", r.Switch.ToString() );
cmd.ExecuteNonQuery();
}
}
trans.Commit();
}
}
await cn.CloseAsync();
}
}
}
关于如何调用此内容的摘录:
private async void button1_Click( object sender, EventArgs e ) {
this.Text = "Adding Records";
await AddRecords();
this.Text = "Completed";
}
顺便说一下,当UI被阻止时,不应该在所有先前代码执行后阻止它。例如,在上面的按钮点击方法中,第一个' this.Text'未设置,因为只要await AddRecords();
执行,它就会在UI有机会完成更新之前发生,并且在阻止完成之后才会完成,这导致只有this.Text - "Completed"
被注意到在UI级别。
更新
在将cmd.ExecuteNonQuery();
修改为await cmd.ExecuteNonQueryAsync();
(由Yuval Itzchakov推荐)之后,用户界面仍处于屏蔽状态,这导致我认为它在trans.Commit();
行阻止或与之相关如何建立交易。
更新了代码
public static async Task AddRecords() {
foreach ( string month in Months ) {
await MakeTable( month );
string query="INSERT INTO `"+month+"` ( Caller, Started, Dialed, DurationSec, DurationMin, Cost, Location, Switch ) VALUES (@Caller, @Started, @Dialed, @DurationSec, @DurationMin, @Cost, @Location, @Switch);";
using ( MySqlConnection cn=new MySqlConnection( ConnectionString.ToString() ) ) {
await cn.OpenAsync();
using ( MySqlTransaction trans=cn.BeginTransaction() ) {
using ( MySqlCommand cmd=new MySqlCommand( query, cn, trans ) ) {
cmd.CommandType=CommandType.Text;
foreach ( Record r in CDR.Records ) {
if ( r.Started.ToString( "yyyy-MM" )==month ) {
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue( "@Caller", r.Caller );
cmd.Parameters.AddWithValue( "@Started", r.Started );
cmd.Parameters.AddWithValue( "@Dialed", r.Dialed );
cmd.Parameters.AddWithValue( "@DurationSec", r.Duration );
cmd.Parameters.AddWithValue( "@DurationMin", Math.Ceiling( r.Duration/60 ) );
cmd.Parameters.AddWithValue( "@Cost", r.Cost );
cmd.Parameters.AddWithValue( "@Location", r.Location );
cmd.Parameters.AddWithValue( "@Switch", r.Switch.ToString() );
await cmd.ExecuteNonQueryAsync();
}
}
trans.Commit();
}
}
await cn.CloseAsync();
}
}
}
答案 0 :(得分:5)
使用ExecuteNonQueryAsync
代替ExecuteNonQuery
:
await cmd.ExecuteNonQueryAsync();
这是您查询的最耗时的调用,由于您正在使用同步版本而被同步阻止。
修改强>
由于您不需要执行任何需要同步上下文的UI工作,您可以使用ConfigureAwait(false)
,您可以将其应用于OpenAsync
,然后继续将在线程池上运行IO工作人员。
答案 1 :(得分:1)
我认为大部分时间都花在Commit
操作上。问题是await
函数中的每个AddRecords
都会同步回调用函数的UI线程。这是你的主要问题。
与导致您遇到麻烦的SynchronizationContext
断开连接的最简单方法是强制AddRecords
在ThreadPool
- 主题中运行。
这样做很简单:
private async void button1_Click( object sender, EventArgs e ) {
this.Text = "Adding Records";
await Task.Run(() => AddRecords());
this.Text = "Completed";
}
这将在ThreadPool
中运行您的数据库内容,只有您在此处创建的Task
在完成所有操作后才会同步回UI。
缺点是,您无法再从AddRecords
方法中访问用户界面。但是在你的代码中你没有做到这一点,所以我想这很好。