解析大量 Excel 文件失败
Posted
技术标签:
【中文标题】解析大量 Excel 文件失败【英文标题】:Parsing Large List of Excel Files Failing 【发布时间】:2014-06-27 20:26:43 【问题描述】:这是一个 C#/VSTO 程序。我一直在做一个数据采集项目。范围基本上是“处理各种第三方公司发送的 Excel 文件”。实际上,这意味着:
-
通过搜索方法找到包含我想要的数据的列。
从工作簿中获取数据
清理数据、运行一些计算等
将清理后的数据输出到新工作簿中
我编写的程序非常适合中小型数据集,大约 25 个工作簿,总共有大约 1000 行相关数据。我从这些工作簿中抓取了 7 列数据。不过,我遇到的一个极端情况是,有时我需要运行一个更大的数据集,大约 50 个工作簿,总共有大约 8,000 行相关数据(可能还有大约 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
来手动减慢速度,但这只会让失败发生得更远。
我关闭工作簿,退出应用程序,然后 ReleaseComObject
到 Excel 对象,GC.Collect
和 GC.WaitForPendingFinalizers
在每个循环结束时。
我目前的想法是:
-
将列表分成两半并分别运行
并行打开一些
new Excel.Application()
,但在该 Excel 实例中按顺序运行文件列表(有点像 #1,但使用不同的路径)
按文件大小分隔列表,并独立/按顺序运行一小组非常大的文件,其余部分照常运行
我希望得到一些帮助的事情:
-
关于确保我的记忆被清除的建议(也许
Process.Id
在所有的开头和结尾都被扭曲了?)
关于订购并行流程的建议 - 我想知道是否可以先让“大”人加入,这将使运行时间更长的流程更加稳定。
我一直在查看:http://reedcopsey.com/2010/01/26/parallelism-in-net-part-5-partitioning-of-work/,他说“根据您的工作的先验知识,可能比默认分区器更有意义地对数据进行分区。”但是我很难真正知道什么/如果分区是有意义的。
非常感谢任何见解!
更新
因此,作为一般规则,我会针对 Excel 2010 进行测试,因为我们这里同时使用了 2010 和 2013。我在 2013 年运行它,它运行良好 - 运行时间大约 4 分钟,这与我预期的差不多。在我放弃 2010 兼容性之前,还有其他想法吗? 2010机器是64位机器加64位Office,2013机器是64位机器加32位Office。这有关系吗?
【问题讨论】:
我想知道死锁是不是因为你已经用尽了线程池中的所有可用线程?您可以尝试使用 ThreadPool.SetMaxThreads 增加限制,看看是否有帮助。 如果每个工作簿包含少于 10,000 行(根据您的问题定义),则要读取 50 个 Excel 文件实际上并不是什么大问题。尝试优化您的文件阅读器,特别是将 Excel 范围转换为 C# 数组;一旦您将 Excel 范围转换为 C# 数组,其余处理应该运行得非常快(通常)。 Rgds, 谢谢!刚开始工作 - 让我研究一下这两个想法并报告 @AlexBell 所以你推荐使用object[,] values = range.Value as object[,]
并从那里处理它。我的想法是让我快速获取数据并让 Excel 实例不碍事?
是的,像这样:如果你知道数据类型,那么数组可以是强类型的(例如 string[] 或 string[,] 或 string[][] 哪个是最好的适合您的情况)。 Rgds,
【参考方案1】:
几年前,我使用 excel 文件和自动化。然后我在任务管理器中有僵尸进程的问题。虽然我们的程序结束了,我认为我正确退出了 excel,但进程并没有退出。
我不喜欢这个解决方案,但它很有效。我可以这样总结解决方案。
1) 永远不要连续使用两个点,例如:
workBook.ActiveSheet.PageSetup
改为使用变量.. 完成后释放它们并将它们归零。
示例:而不是这样做:
m_currentWorkBook.ActiveSheet.PageSetup.LeftFooter = str.ToString();
遵循此功能中的做法。 (此功能在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) 为了创建 excel 文件,您可以使用 excel 应用程序,但是要从 excel 中获取数据,我建议使用 OleDB。您可以像数据库一样使用 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;
【讨论】:
我有很多结构很差的数据,而且我并不总是知道我的数据在哪里,因此我的第一步是定位数据。为了确保我遵循,您的建议是我通过调用所有这些电子表格(每个文件的 connectionString 1 次迭代)来构建我的数据库/数据集,然后执行我的 4 个步骤?【参考方案2】:作为@Mustafa Düman 提出的替代解决方案,我建议您使用Version 4 beta of EPPlus。我在几个项目中使用它没有问题。
优点:
快速 没有内存泄漏(对于版本 不需要在您使用 Office 的机器上安装 Office缺点:
只能用于 .xlsx 文件(Excel 2007 / 2010)我在 20 个 12.5 MB 左右的 excel 文件(每个文件中有超过 50k 条记录)上使用以下代码对其进行了测试,我认为提及它没有崩溃就足够了 :)
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();
【讨论】:
嘿-我很抱歉没有在这里回复。非常酷的包。由于其他一些参数,我不能将它用于这个特定的项目,但我以后会把它放在我的后兜里。谢谢!以上是关于解析大量 Excel 文件失败的主要内容,如果未能解决你的问题,请参考以下文章