如何在多线程处理Excel DataRow时正确捕获行索引?
解决多线程处理DataRow时的行索引捕获问题
首先,你当前的实现存在线程安全隐患:多个线程共享同一个int i变量,i++不是原子操作,多个线程同时执行会导致索引值被覆盖、计数错误,日志里的行号会完全不准确。而且foreach遍历IEnumerable<DataRow>时本身没有内置索引,结合多线程场景,必须换一种线程安全的方式来获取每个行对应的原始索引。
核心思路
在进入多线程处理之前,先给每一行DataRow绑定它的原始行索引(对应Excel表格里的真实行号,包含表头计数),这样每个线程处理自己的行数据时,直接使用预绑定好的索引,不需要跨线程修改共享变量,彻底避免线程冲突。
修正后的实现步骤
- 预处理数据,绑定原始索引:把
IEnumerable<DataRow>转换成包含行对象和原始索引的集合,同时跳过前5行表头(对应原始索引0-4,从索引5开始处理)。 - 移除共享变量
i:每个线程只处理自己的行数据和对应的索引,不需要跨线程修改共享变量。 - 修复NHibernate Session的资源泄漏:用
using块自动释放Session,确保线程结束后资源被正确回收。 - 修正代码拼写错误:比如你代码里的
linha.ItemArray[2]应该是line.ItemArray[2](变量名拼写不一致)。
修正后的代码示例
private System.Threading.Tasks.Task TableProcessing(IEnumerable<DataRow> dataparam, User UserLogged) { // 预处理:给每一行绑定原始Excel行号,跳过前5行表头 var indexedRows = dataparam .Select((row, originalIndex) => new { Row = row, RowNumber = originalIndex + 1 }) // originalIndex从0开始,+1对应Excel的行号 .Skip(5); // 跳过前5行表头(行号1-5) return Task.WhenAll(indexedRows.Select(rowWithIndex => Task.Run(() => { // 使用using块确保NHibernate Session自动释放,避免资源泄漏 using (var session = NhibernateHelper.HelpThreading().OpenSession()) { NHibernate.Context.ThreadStaticSessionContext.Bind(session); var line = rowWithIndex.Row; var currentLineNumber = rowWithIndex.RowNumber; // 直接使用预绑定的行号,线程安全 List<Process> listProcessExisting = new List<Process>(); Process process = null; Interested interested = new Interested(); // 按当前或之前的司法编号查询流程 var currentProcessNum = line.ItemArray[1].ToString(); if (!string.IsNullOrEmpty(currentProcessNum)) process = aplProcess.consultPerNProcessER(currentProcessNum); if (process == null) { var prevProcessNum = line.ItemArray[2].ToString(); if (!string.IsNullOrEmpty(prevProcessNum)) process = aplProcess.consultPerNProcessER(prevProcessNum); // 修复拼写错误:linha -> line } // 按CPF/CNPJ查询相关方 var idValue = line.ItemArray[7].ToString(); if (idValue.Length == 14) interested = aplInterested.ConsultPerCPF(AuxiliarCPF_CNPJ.DeformationCPF(idValue)); else if (idValue.Length == 18) interested = aplInterested.ConsultPerCPF(HelpCPF_CNPJ.DeformationCNPJ(idValue)); if (process != null) { // 查询流程关联的所有相关方 List<ProcessInterested> listprocessinterested = aplProcessInterested.consultPerIdProcess(process.Code); if (listprocessinterested.Exists(o => o.Interested == interested)) { log.Append($"The interested {process.Interested.Name}, entered in the line {currentLineNumber} was added back to the process {process.NumberProcessjudicial} <br>"); } setProcessInterested(process, interested, line, currentLineNumber); } else { // 创建新流程 process = new Process(); process.Interested = interested; // 可在此添加新流程的保存逻辑 } } }))); }
关键改动说明
- 线程安全的索引获取:通过
Select((row, originalIndex) => ...)提前给每一行绑定行号,每个线程处理自己的行数据时直接使用currentLineNumber,完全避免了共享变量的竞争问题。 - 资源安全:用
using块包裹NHibernate Session,确保线程执行完毕后Session被正确关闭和释放,避免内存泄漏。 - 代码健壮性:用
string.IsNullOrEmpty替代直接判断Equals(""),逻辑更严谨;修正了变量拼写错误,避免运行时异常。
内容的提问来源于stack exchange,提问作者user9789539
相关产品推荐
相关产品推荐

