parsing大型Excel文件列表失败

这是一个C#/ VSTO程序。 我一直在做一个数据捕获项目。 范围基本上是“处理各种第三方公司发送的Excel文件”。 实际上,这意味着:

  1. 通过search方法find包含我想要的数据的列。
  2. 从工作簿中获取数据
  3. 清理数据,运行一些计算等
  4. 将清理的数据输出到新的工作簿中

我写的程序对于中小型数据集非常有用,大约有25个工作簿,总共有大约1000行相关数据。 我从这些工作簿中抓取了7列数据。 我有一个边缘案例,有时候我需要运行一个更大的数据集,大约50个工作簿,共有约8000行相关数据(可能还有2000个重复数据,我也必须删除)。

我目前正在通过一个Parallel.ForEach循环里面的文件列表,我打开一个new Excel.Application()处理每个文件与多个ActiveSheet s。 在较小的数据集上,并行处理的运行速度要比顺序处理的要快得多。 但是在更大的数据集上,我似乎碰壁了。

我开始收到消息: Microsoft Excel is waiting for another application to complete an OLE action ,最终它只是失败。 切换回顺序foreach确实允许程序完成,但它只是磨合 – 从1至3分钟的平行中等规模的数据集到20分钟以上的连续大型数据集。 如果我把ParallelOptions.MaxDegreeOfParallelism设置为10,它会完成这个循环,但是仍然需要15分钟。 如果我将它设置为15,则失败。 如果我不需要,我也不喜欢弄乱TPL设置。 我也尝试过插入一个Thread.Sleep来手动减慢速度,但是这只会让失败发生。

我closures工作簿,退出应用程序,然后将GC.WaitForPendingFinalizers到每个循环结束时的Excel对象和GC.CollectGC.WaitForPendingFinalizers

我现在的想法是:

  1. 将列表分成两半,分别运行
  2. 并行打开一些new Excel.Application() ,但是在该Excel实例内依次运行一个文件列表(有点像#1,但使用不同的path)
  3. 按文件大小分开列表,并独立/按顺序运行一小组非常大的文件,像以前一样运行其余的文件

我希望得到一些帮助:

  1. 关于如何确保我的记忆被清除的build议(也许Process.Id在所有的开始和结束都被扭曲了?)
  2. 关于订购一个并行stream程的build议 – 我想知道如果我能把这个“大”的人放在第一位,这将使长期运行的stream程更加稳定。

我一直在看: http : //reedcopsey.com/2010/01/26/parallelism-in-net-part-5-partitioning-of-work/ ,他说:“事先了解你的工作,可能是可能比默认的分区程序更有意义的分区数据。“ 但是我真的很难知道分区是有意义的。

真的很感谢任何见解!

UPDATE

所以作为一般的规则,我testing了Excel 2010,因为我们在这里使用2010和2013。 我在2013年运行它,它工作正常 – 运行时间约4分钟,这是我所期望的。 在我放弃2010兼容性之前,还有其他想法吗? 2010机器是64位Office的64位机器,而2013机器是具有32位Office的64位机器。 这一切是否重要?

几年前,我与Excel文件和自动化工作。 然后我有在任务pipe理器中有僵尸进程的问题。 虽然我们的计划结束了,我还以为自己放弃了正确的工作,这个过程并没有退出。

这个解决scheme不是我喜欢的,但它是有效的。 我可以总结这样的解决scheme。

1)不要连续使用两个点,如:

 workBook.ActiveSheet.PageSetup 

而是使用variables..当你完成relase和空他们。

例如:而不是这样做:

 m_currentWorkBook.ActiveSheet.PageSetup.LeftFooter = str.ToString(); 

遵循这个function的做法。 (此function为excel页脚添加条形码。)

  private bool SetBarcode(string text) { Excel._Worksheet sheet; sheet = (Excel._Worksheet)m_currentWorkbook.ActiveSheet; try { StringBuilder str = new StringBuilder(); str.Append(@"&""IDAutomationHC39M,Regular""&22("); str.Append(text); str.Append(")"); Excel.PageSetup setup; setup = sheet.PageSetup; try { setup.LeftFooter = str.ToString(); } finally { RemoveReference(setup); setup = null; } } finally { RemoveReference(sheet); sheet = null; } return true; } 

这里是RemoveReference函数(把null放在这个函数中是行不通的)

  private void RemoveReference(object o) { try { System.Runtime.InteropServices.Marshal.ReleaseComObject(o); } catch { } finally { o = null; } } 

如果你遵循这个模式,它保证没有泄漏,没有僵尸进程等。

2)为了创buildExcel文件,你可以使用Excel的应用程序,但是从Excel中获取数据,我build议使用OleDB。 你可以像Excel数据库一样处理excel,并从sql查询,数据表等中获取数据。

示例代码:(而不是填充数据集,您可以使用datareader进行内存性能)

  private List<DataTable> getMovieTables() { List<DataTable> movieTables = new List<DataTable>(); var connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + excelFilePath + ";Extended Properties=\"Excel 12.0;IMEX=1;HDR=NO;TypeGuessRows=0;ImportMixedTypes=Text\""; ; using (var conn = new OleDbConnection(connectionString)) { conn.Open(); DataRowCollection sheets = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" }).Rows; foreach (DataRow sheet in sheets) { using (var cmd = conn.CreateCommand()) { cmd.CommandText = "SELECT * FROM [" + sheet["TABLE_NAME"].ToString() + "] "; var adapter = new OleDbDataAdapter(cmd); var ds = new DataSet(); try { adapter.Fill(ds); movieTables.Add(ds.Tables[0]); } catch (Exception ex) { //Debug.WriteLine(ex.ToString()); continue; } } } } return movieTables; } 

作为@MustafaDüman提出的替代解决scheme,我build议您使用EPPlus的第4版testing版 。 我在几个项目中使用它没有问题。

优点:

  • 快速
  • 没有内存泄漏(我不能告诉相同的版本<4)
  • 不要求在您使用它的机器上安装Office

缺点:

  • 只能用于.xlsx文件(Excel 2007/2010)

我用下面的代码testing了20个excel文件,每个文件大约12.5 MB(每个文件超过50k条logging),我想这足以说明它没有崩溃:)

  Console.Write("Path: "); var path = Console.ReadLine(); var dirInfo = new DirectoryInfo(path); while (string.IsNullOrWhiteSpace(path) || !dirInfo.Exists) { Console.WriteLine("Invalid path"); Console.Write("Path: "); path = Console.ReadLine(); dirInfo = new DirectoryInfo(path); } string[] files = null; try { files = Directory.GetFiles(path, "*.xlsx", SearchOption.AllDirectories); } catch (Exception ex) { Console.WriteLine(ex.Message); Console.ReadLine(); return; } Console.WriteLine("{0} files found.", files.Length); if (files.Length == 0) { Console.ReadLine(); return; } int succeded = 0; int failed = 0; Action<string> LoadToDataSet = (filePath) => { try { FileInfo fileInfo = new FileInfo(filePath); using (ExcelPackage excel = new ExcelPackage(fileInfo)) using (DataSet dataSet = new DataSet()) { int workSheetCount = excel.Workbook.Worksheets.Count; for (int i = 1; i <= workSheetCount; i++) { var worksheet = excel.Workbook.Worksheets[i]; var dimension = worksheet.Dimension; if (dimension == null) continue; bool hasData = dimension.End.Row >= 1; if (!hasData) continue; DataTable dataTable = new DataTable(); //add columns foreach (var firstRowCell in worksheet.Cells[1, 1, 1, dimension.End.Column]) dataTable.Columns.Add(firstRowCell.Start.Address); for (int j = 0; j < dimension.End.Row; j++) dataTable.Rows.Add(worksheet.Cells[j + 1, 1, j + 1, dimension.End.Column].Select(erb => erb.Value).ToArray()); dataSet.Tables.Add(dataTable); } dataSet.Clear(); dataSet.Tables.Clear(); } Interlocked.Increment(ref succeded); } catch (Exception) { Interlocked.Increment(ref failed); } }; Stopwatch sw = new Stopwatch(); sw.Start(); files.AsParallel().ForAll(LoadToDataSet); sw.Stop(); Console.WriteLine("{0} succeded, {1} failed in {2} seconds", succeded, failed, sw.Elapsed.TotalSeconds); Console.ReadLine();