You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForm中如何用BackgroundWorker从数据库填充DataTable并显示进度

在BackgroundWorker中填充DataTable时实时显示进度

你遇到的问题很典型——DataAdapter.Fill()是一次性完成DataTable填充的,没法直接中途报告进度。要实现实时进度更新,我们需要换一种方式读取数据库数据,逐行处理并定期上报进度。下面是具体的实现思路和代码示例:


核心思路

  • 先查询获取总行数(如果你的查询支持的话),这样能准确计算进度百分比
  • 使用DataReader逐行读取数据,每读取一定数量的行就调用ReportProgress更新进度
  • 利用BackgroundWorker的ProgressChanged事件(自动在UI线程执行)更新进度窗体的内容

修改后的完整代码示例

1. DoWork事件(后台线程执行数据库操作)

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    string connectionString = "你的数据库连接字符串";
    string query = "SELECT * FROM 你的表名";
    string countQuery = "SELECT COUNT(*) FROM 你的表名"; // 先获取总行数

    int totalRows = 0;
    int currentRow = 0;

    // 第一步:获取总行数(用于计算进度)
    using (SqlConnection conn = new SqlConnection(connectionString))
    {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand(countQuery, conn))
        {
            totalRows = Convert.ToInt32(cmd.ExecuteScalar());
        }
    }

    // 第二步:逐行读取数据并填充DataTable
    DataTable dt = new DataTable();
    using (SqlConnection conn = new SqlConnection(connectionString))
    {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand(query, conn))
        {
            using (SqlDataReader reader = cmd.ExecuteReader())
            {
                // 先加载DataTable的结构
                dt.LoadSchema(reader);

                while (reader.Read())
                {
                    // 检查是否取消操作
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        return;
                    }

                    // 将当前行添加到DataTable
                    DataRow row = dt.NewRow();
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        row[i] = reader.IsDBNull(i) ? DBNull.Value : reader[i];
                    }
                    dt.Rows.Add(row);

                    // 每读取100行(可根据实际调整)报告一次进度
                    currentRow++;
                    if (currentRow % 100 == 0 || currentRow == totalRows)
                    {
                        int progress = (int)Math.Round((double)currentRow / totalRows * 100);
                        worker.ReportProgress(progress, $"已处理 {currentRow}/{totalRows} 行");
                    }
                }
            }
        }
    }

    // 将最终的DataTable作为结果返回
    e.Result = dt;
}

2. ProgressChanged事件(UI线程更新进度)

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // 更新进度条和提示文本
    alert.Message = $"正在加载数据... {e.ProgressPercentage}% ({e.UserState.ToString()})";
    alert.ProgressValue = e.ProgressPercentage;
}

3. RunWorkerCompleted事件(完成后的清理)

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        alert.Message = "操作已取消";
    }
    else if (e.Error != null)
    {
        alert.Message = $"加载失败:{e.Error.Message}";
        // 可以在这里添加错误日志或提示框
    }
    else
    {
        // 获取填充好的DataTable并使用
        DataTable resultDt = e.Result as DataTable;
        // 比如绑定到DataGridView:dataGridView1.DataSource = resultDt;
        alert.Message = "数据加载完成!";
    }

    // 延迟关闭提示窗体,让用户看到最终状态
    System.Threading.Thread.Sleep(500);
    alert.Close();
    button1.Enabled = true;
}

注意事项

  • 总行数查询的性能:如果你的表非常大,COUNT(*)可能会很慢,可以考虑用数据库的快速计数方式(比如SQL Server的sys.dm_db_partition_stats)
  • 批量报告进度:不要每读取一行就报告一次,这样会频繁触发UI更新,影响性能。建议每100/500行报告一次,根据实际情况调整
  • 线程安全:BackgroundWorker的ReportProgress会自动切换到UI线程,所以直接更新进度窗体的控件是安全的,不需要额外的Invoke
  • 取消操作:始终在循环中检查CancellationPending,确保用户可以中途取消操作

内容的提问来源于stack exchange,提问作者Usman Farooq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:51:41