使用事务进行批量插入会阻止异步进程上的UI

Kra*_*ime 2 c# mysql asynchronous transactions async-await

尝试使用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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

关于如何调用它的片段:

    private async void button1_Click( object sender, EventArgs e ) {
         this.Text = "Adding Records";
         await AddRecords();
         this.Text = "Completed";
    }
Run Code Online (Sandbox Code Playgroud)

顺便说一句,当UI被阻止时,不应该在所有先前的代码执行之后阻止它.例如,在上面的按钮单击方法中,第一个'this.Text'没有设置,因为一旦await AddRecords();执行,它就会在UI有机会完成更新之前发生,并且在阻塞完成之后才会完成.导致仅this.Text - "Completed"在UI级别被注意到.


UPDATE

在修改cmd.ExecuteNonQuery();await cmd.ExecuteNonQueryAsync();(Yuval Itzchakov推荐)之后,UI仍然处于阻塞状态,这导致我认为它在线上阻塞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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Yuv*_*kov 5

使用ExecuteNonQueryAsync而不是ExecuteNonQuery:

await cmd.ExecuteNonQueryAsync();
Run Code Online (Sandbox Code Playgroud)

这是您查询的最耗时的调用,由于您正在使用同步版本,因此会同步阻止该查询.

编辑:

由于您不需要执行任何需要同步上下文的UI工作,您可以使用ConfigureAwait(false),您可以应用它OpenAsync,然后继续将在线程池IO工作程序上运行.