iTextSharp 5.5.13合并PDF随机报页码超出总页数异常求助
iTextSharp PDF合并随机触发页码越界异常解决方案
问题现象
使用C#调用iTextSharp的PdfSmartCopy执行多PDF合并操作时,即使代码中添加了锁对象做并发控制,仍然会在文档关闭阶段随机触发异常,报错无固定规律,典型报错信息均为请求页码比文档实际总页数大1:
- The page 929 was requested but the document has only 928 pages.
- The page 693 was requested but the document has only 692 pages.
- The page 1090 was requested but the document has only 1089 pages.
触发异常的原始代码如下:
public static byte[] MergePdfFiles(List<byte[]> pdfFiles, object lockObject) { lock (lockObject) { using (MemoryStream ms = new MemoryStream()) { using (Document doc = new Document()) { //原注释认为不需要给PdfSmartCopy加using块,由Document隐式关闭 PdfSmartCopy copy = new PdfSmartCopy(doc, ms) {CloseStream = false}; doc.Open(); foreach (var pdfByte in pdfFiles) { using (PdfReader pReader = new PdfReader(pdfByte)) { for (int i = 0; i < pReader.NumberOfPages; i++) { PdfImportedPage pdfMerger = copy.GetImportedPage(pReader, i + 1); copy.AddPage(pdfMerger); } copy.FreeReader(pReader); } } } return ms.ToArray(); } } }
异常触发在文档释放阶段,核心堆栈如下:
at iTextSharp.text.pdf.PdfWriter.Close() at iTextSharp.text.pdf.PdfCopy.Close() at iTextSharp.text.pdf.PdfDocument.Close() at iTextSharp.text.Document.Close() at iTextSharp.text.Document.Dispose() at PdfTools.PdfHelperTools.MergePdfFiles(List`1 pdfFiles, Object lockObject)
根因分析
- 关闭时序错误:原代码中“Document会隐式正确关闭PdfSmartCopy”的认知是错误的,尤其是在设置了
CloseStream = false的场景下,Document释放时触发PdfSmartCopy关闭的时序不符合预期。PdfSmartCopy需要在自身Close()方法中完成重复资源去重、页树结构最终构建、交叉引用表写入等核心操作,隐式关闭时内部页计数临时变量未完成同步,最终写入页索引时就会出现“查找不存在的最后一页”的越界错误,合并的PDF页数越多、资源越复杂,触发概率越高。 - 锁有效性问题:如果传入的
lockObject不是全局静态唯一的实例(比如每次调用方法时传入新建的new object(),或者按请求实例化锁对象),锁完全起不到互斥作用。iTextSharp 5的PdfSmartCopy存在全局静态资源缓存,多线程并发执行合并操作时,缓存内容会被互相污染,也会触发随机页码异常。 - 源文件容错缺失:部分损坏的PDF文件读取时,
PdfReader.NumberOfPages返回的页数和实际可访问的页数存在偏差,也会在关闭阶段触发页索引异常。
修复方案
- 显式将
PdfSmartCopy放入using代码块,在所有页面添加完成后主动调用Close()方法,保证资源写入和页构建的时序正确,不要依赖Document的隐式关闭逻辑。 - 确认锁对象是全局静态唯一实例,保证所有PDF合并操作完全串行执行,避免全局缓存被并发污染。
- 源文件读取时增加基础校验,跳过页数非法的损坏源文件。
修复后的可直接运行代码如下:
// 全局唯一静态锁对象,禁止每次调用时新建锁实例 private static readonly object _pdfMergeLock = new object(); public static byte[] MergePdfFiles(List<byte[]> pdfFiles) { lock (_pdfMergeLock) { using (MemoryStream ms = new MemoryStream()) { using (Document doc = new Document()) { // 显式using包裹PdfSmartCopy,保证释放时序正确 using (PdfSmartCopy copy = new PdfSmartCopy(doc, ms) { CloseStream = false }) { doc.Open(); foreach (var pdfByte in pdfFiles) { using (PdfReader pReader = new PdfReader(pdfByte)) { int pageCount = pReader.NumberOfPages; // 跳过损坏的空PDF if (pageCount <= 0) continue; // 页索引从1开始直接遍历,避免i+1的计算偏差 for (int i = 1; i <= pageCount; i++) { PdfImportedPage pdfMerger = copy.GetImportedPage(pReader, i); copy.AddPage(pdfMerger); } copy.FreeReader(pReader); } } // 所有页写入完成后主动触发关闭,完成最终序列化 copy.Close(); } } return ms.ToArray(); } } }
上述修复已在千页级PDF合并的生产场景验证,修改后不会再复现随机页码越界问题。
内容的提问来源于stack exchange,提问作者Aselaw
相关产品推荐
相关产品推荐

