DataGridView勾选行计数异常求助:导出Excel行号错误
问题分析与解决建议
嘿,我来帮你拆解下这个问题——你导出Excel时所有行号都变成总勾选数的核心原因,是并行执行中的竞态条件,再加上Excel COM对象本身的线程安全限制,咱们一步步来解决:
问题根源
你用Parallel.ForEach来并行处理行,但checkedRowCount是一个共享的普通整型变量。多个线程会同时读取和修改这个变量:比如线程A和线程B同时读到checkedRowCount=14,然后都把它递增到15,接着都往Excel的第15行写数据,最后就出现了所有行都是同一个序号的情况。
另外要划重点:Excel的COM对象完全不是线程安全的,并行操作同一个Worksheet本身就可能引发各种不可预测的bug(比如数据错乱、程序崩溃),这也是你单独测试可能正常(单线程环境),但实际场景出问题的关键原因。
解决方案
方案1:用线程安全的计数器(临时救急,但仍有风险)
如果一定要用并行,可以用Interlocked.Increment来原子性地递增计数,确保每个线程拿到唯一的序号:
int checkedRowCount = 0; // ... 省略SaveFileDialog和Excel初始化代码 Parallel.ForEach(dataGridView1.Rows.Cast<DataGridViewRow>(), row => { DataGridViewCell cbCell = row.Cells[6]; if (!(cbCell.Value is DBNull)) { if (Convert.ToBoolean(cbCell.Value)) { // 原子性递增,避免多线程同时修改的竞态问题 int currentRow = Interlocked.Increment(ref checkedRowCount); for (int i = 0; i < dataGridView1.Columns.Count - 1; i++) { // 注意:这里仍存在Excel对象线程不安全的风险,不推荐长期用 xlWS.Cells[currentRow, i + 1] = dataGridView1.Rows[row.Index].Cells[i].Value?.ToString() ?? string.Empty; } } } });
方案2:改用普通循环(最稳妥,推荐)
既然Excel不支持多线程操作,直接放弃并行,用普通的foreach循环反而更稳定,还能彻底解决行号问题:
int checkedRowCount = 0; // ... 省略SaveFileDialog和Excel初始化代码 foreach (DataGridViewRow row in dataGridView1.Rows.Cast<DataGridViewRow>()) { DataGridViewCell cbCell = row.Cells[6]; // 简化条件判断,同时增加空值保护 if (!(cbCell.Value is DBNull) && Convert.ToBoolean(cbCell.Value)) { checkedRowCount++; for (int i = 0; i < dataGridView1.Columns.Count - 1; i++) { // 用?.ToString()避免NullReferenceException,空值时显示空字符串 xlWS.Cells[checkedRowCount, i + 1] = row.Cells[i].Value?.ToString() ?? string.Empty; } } }
额外优化:先筛选再处理(代码更清晰)
你可以先把所有勾选的行筛选出来,再批量写入Excel,逻辑更直观:
// 先筛选出所有勾选的行 var checkedRows = dataGridView1.Rows.Cast<DataGridViewRow>() .Where(row => !(row.Cells[6].Value is DBNull) && Convert.ToBoolean(row.Cells[6].Value)) .ToList(); // 遍历筛选后的行写入Excel for (int rowIdx = 0; rowIdx < checkedRows.Count; rowIdx++) { var targetRow = checkedRows[rowIdx]; for (int colIdx = 0; colIdx < dataGridView1.Columns.Count - 1; colIdx++) { // 行号从1开始,所以是rowIdx+1 xlWS.Cells[rowIdx + 1, colIdx + 1] = targetRow.Cells[colIdx].Value?.ToString() ?? string.Empty; } }
内容的提问来源于stack exchange,提问作者Alexander Moiseev
相关产品推荐
相关产品推荐

