如何在BackgroundWorker中更新DataTable记录并解决重复执行报错问题
首先,你遇到的"This BackgroundWorker is currently busy..."报错原因很直接:当BackgroundWorker正在执行后台任务时,你再次调用了RunWorkerAsync()。它同一时间只能处理一个任务,所以得先堵住重复点击的问题,然后还要把你的代码逻辑调整得更合理——你原来的写法把耗时的数据库更新放在了主线程,BackgroundWorker只是在模拟进度,这完全颠倒了它的用途。
下面一步步给你解决:
核心修复点
1. 阻止重复点击触发任务
在按钮的点击事件里,先检查BackgroundWorker的IsBusy状态,如果它正在忙,直接提示用户或者返回,别让新任务启动:
if (backgroundWorker1.IsBusy) { MessageBox.Show("更新任务正在进行中,请稍候!"); return; }
2. 把耗时的数据库操作移到后台线程
你原来的代码在主线程做批量SQL更新,这会导致UI卡顿,BackgroundWorker的核心作用就是把这类耗时操作扔到后台,主线程专注于更新UI(比如进度条)。所以要把数据库更新的逻辑全部移到DoWork事件里。
3. 正确配置BackgroundWorker属性
WorkerReportsProgress要在任务启动前设置好,别在DoWork的循环里反复设置——最好在窗体初始化时就配置:
public YourFormName() { InitializeComponent(); backgroundWorker1.WorkerReportsProgress = true; backgroundWorker1.WorkerSupportsCancellation = true; // 可选,允许用户取消任务 }
4. 线程安全地传递UI数据
后台线程不能直接访问UI控件(比如dataGridView1、company_name文本框),会引发跨线程异常。所以要把需要的参数打包成对象,通过RunWorkerAsync()传递给后台。
5. 准确计算进度百分比
你原来的进度计算是直接用循环次数当百分比,如果DataGridView的行数不是100,进度条永远到不了100%。正确的计算方式是:(已处理行数 / 总行数) * 100。
6. 任务完成后的UI更新要放在RunWorkerCompleted
别在主线程里直接弹MessageBox,因为后台任务还没完成就会触发提示,要等后台任务结束后,在RunWorkerCompleted事件里处理结果和提示。
完整修正后的代码
窗体初始化配置
public YourFormName() { InitializeComponent(); // 提前配置BackgroundWorker的属性 backgroundWorker1.WorkerReportsProgress = true; backgroundWorker1.WorkerSupportsCancellation = true; }
按钮点击事件
private void btnStartUpdate_Click(object sender, EventArgs e) { // 防止重复触发任务 if (backgroundWorker1.IsBusy) { MessageBox.Show("更新任务正在进行中,请稍候!"); return; } // 打包需要传递给后台的参数(避免后台线程直接访问UI控件) var updateParameters = new { CompanyName = company_name.Text, IndebtednessName = indebtedness_name.Text, RowsToUpdate = dataGridView1.Rows.Cast<DataGridViewRow>() .Where(row => !row.IsNewRow) .Select(row => new { SubscriberNo = Convert.ToInt64(row.Cells[0].Value), CollectedAmount = Convert.ToDecimal(row.Cells[1].Value), PaymentDate = row.Cells[2].Value // 确保是DateTime类型,若为字符串请用DateTime.Parse转换 }) .ToList() }; // 初始化UI状态 progressBar1.Show(); label6.Text = "0%"; label7.Text = ""; // 启动后台任务 backgroundWorker1.RunWorkerAsync(updateParameters); }
DoWork后台任务(核心数据库更新逻辑)
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { var worker = sender as BackgroundWorker; var paramsData = e.Argument as dynamic; // 也可以定义一个强类型类,更规范 int totalRows = paramsData.RowsToUpdate.Count; int successCount = 0; using (SqlConnection con = new SqlConnection("你的数据库连接字符串")) { con.Open(); // 预先创建SQL命令和参数,避免循环中重复创建 using (SqlCommand com = new SqlCommand(@"UPDATE indebtedness SET collected = @collected, Payment_Date = @Payment_Date, Payment_Date2 = @Payment_Date2 WHERE Subscriber_No = @Subscriber_No AND company_name = @company_name AND indebtedness_name = @indebtedness_name", con)) { com.Parameters.AddWithValue("@company_name", paramsData.CompanyName); com.Parameters.AddWithValue("@indebtedness_name", paramsData.IndebtednessName); com.Parameters.AddWithValue("@Payment_Date2", DateTime.Today); com.Parameters.Add("@Payment_Date", SqlDbType.Date); com.Parameters.Add("@Subscriber_No", SqlDbType.BigInt); com.Parameters.Add(new SqlParameter("@collected", SqlDbType.Decimal) { Precision = 18, Scale = 3 }); for (int i = 0; i < totalRows; i++) { // 检查是否用户取消了任务(可选) if (worker.CancellationPending) { e.Cancel = true; return; } var rowData = paramsData.RowsToUpdate[i]; // 更新参数值 com.Parameters["@Subscriber_No"].Value = rowData.SubscriberNo; com.Parameters["@collected"].Value = rowData.CollectedAmount; com.Parameters["@Payment_Date"].Value = rowData.PaymentDate; // 执行更新 int affectedRows = com.ExecuteNonQuery(); successCount += affectedRows; // 计算并报告进度 int progressPercent = (int)((i + 1) * 100.0 / totalRows); worker.ReportProgress(progressPercent, successCount); // 附带成功更新的行数 // 可选:如果数据库操作太快,进度条闪得太快,可以加个短暂延迟 // Thread.Sleep(10); } } } // 把成功行数作为结果返回给完成事件 e.Result = successCount; }
ProgressChanged进度更新事件
private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { progressBar1.Value = e.ProgressPercentage; label6.Text = $"{e.ProgressPercentage}%"; // 可选:显示已成功更新的行数 int updatedRows = (int)e.UserState; // label8.Text = $"已更新 {updatedRows} 行"; if (e.ProgressPercentage == 100) { label7.Text = "记录更新成功..."; } }
RunWorkerCompleted任务完成事件
private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Error != null) { // 处理后台任务中的异常 MessageBox.Show($"更新出错:{e.Error.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } else if (e.Cancelled) { label7.Text = "更新任务已取消"; } else { int totalSuccess = (int)e.Result; MessageBox.Show($"成功更新 {totalSuccess} 行!", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information); } // 可选:隐藏进度条或重置状态 // progressBar1.Hide(); }
这样调整后,不仅解决了重复点击的报错问题,还真正实现了后台批量更新+实时进度条的功能,UI也不会卡顿了。
内容的提问来源于stack exchange,提问作者Ahmed Alkhteeb

