使用事务的批量插入阻塞了异步进程的UI

本文关键字:异步 进程 UI 插入 事务 | 更新日期: 2023-09-27 18:02:06

尝试使用async/await和事务将多条记录插入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推荐)后,UI仍然阻塞,这使我相信它阻塞在trans.Commit();行或与如何构建事务有关。

<<p> 更新代码/strong>
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();
        }
    }
}

使用事务的批量插入阻塞了异步进程的UI

使用ExecuteNonQueryAsync代替ExecuteNonQuery:

await cmd.ExecuteNonQueryAsync();

这是查询中最耗时的调用,因为您使用的是同步版本,所以它被同步阻塞。

编辑:

因为你不需要做任何需要同步上下文的UI工作,你可以使用ConfigureAwait(false),你可以将其应用于OpenAsync,然后continuation将在线程池IO worker上运行

我认为大部分时间都花在Commit操作上。问题是AddRecords函数中的每个await都同步回调用该函数的UI线程。这是你的主要问题。

断开SynchronizationContext最简单的方法是强制AddRecordsThreadPool线程中运行。

这很简单:

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

这将在ThreadPool中运行你的数据库,一旦一切完成,只有你在这里创建的Task才会同步回UI。

这样做的缺点是,您不能再从AddRecords方法中访问UI了。但是在你的代码中你没有这样做,所以我想这是好的