You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#使用iText7合并PDF后内存未释放问题及Close()作用咨询

C# iText7 批量PDF合并内存泄漏修复

问题现象

在C#环境下使用iText7开发文件夹批量PDF合并功能,交互逻辑为点击按钮弹出FolderBrowserDialog对话框,用户选中目标文件夹后,程序遍历文件夹内所有PDF文件执行合并。功能可正常运行,但合并完成后已处理的PDF资源始终驻留内存未释放,进程内存占用随合并操作次数持续上涨,各阶段内存占用如下:

  • C#进程初始内存占用:C#进程初始内存占用
  • 合并第一个文件夹后内存占用:合并第一个文件夹后内存占用
  • 合并第二个文件夹后内存占用:合并第二个文件夹后内存占用
  • 任务管理器显示的进程内存占用:任务管理器进程内存占用

此前误以为调用Close()方法即可将对应资源从内存中清除,但实际未达到预期效果,需要明确Close()方法的实际作用,同时修复内存持续上涨的问题。

原有问题代码

private void button1_Click(object sender, EventArgs e)
{
    folderBrowserDialog1 = new FolderBrowserDialog();
    DialogResult = folderBrowserDialog1.ShowDialog();

    if(DialogResult == DialogResult.OK)
    {
        textBox1.Text = folderBrowserDialog1.SelectedPath;
        Environment.SpecialFolder root = folderBrowserDialog1.RootFolder;
    }

    if(folderBrowserDialog1.SelectedPath != "")
    {
        
        IList<string> inputFiles = Directory.GetFiles(folderBrowserDialog1.SelectedPath);

        //合并文件输出路径
        string dest = @"D:\PDF-merge";
        string filename = "merged.pdf";

        try
        {
            System.IO.Directory.CreateDirectory(dest);
        }
        catch { }

        //初始化合并目标文档
        PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest + @"\" + filename));
        PdfMerger merger = new PdfMerger(pdfDoc);
        
        PdfReader reader;
        PdfDocument pdf2merge;

        for (int i = 0; i < inputFiles.Count; i++)
        {
            if (Path.GetExtension(inputFiles[i]) == ".pdf")
            {
                reader = new PdfReader(inputFiles[i]);
                reader.SetUnethicalReading(true);
                pdf2merge = new PdfDocument(reader);
                merger.Merge(pdf2merge, 1, pdf2merge.GetNumberOfPages());

                reader.Close();
                pdf2merge.Close();
            }
        }
        
        merger.Close();
        
    }
}

核心原因说明

Close()方法的实际作用

iText7中Close()方法的核心逻辑是:释放关联的非托管资源(文件句柄、流锁)、将内存缓存中未持久化的数据刷入底层流、关闭关联的IO流。它不会立刻强制.NET垃圾回收器回收托管堆上的对象内存,托管内存的回收由GC按自身调度策略触发,这也是调用Close后内存不会立刻下降的原因之一,但原代码存在更直接的资源泄漏点:

  1. 输出端的PdfDocument pdfDoc全程未调用Close()/Dispose():关闭PdfMerger不会自动释放其绑定的输出PdfDocument实例,最终输出文档的资源会持续被占用。
  2. 手动调用Close的逻辑存在风险:如果合并过程中抛出异常(比如损坏的PDF文件、文件被占用),Close方法不会被执行,对应资源会直接泄漏。
  3. 未开启PdfMerger的自动关闭源文档配置,源文档的部分页面缓存资源不会在单个文件合并完成后立刻释放。

修复方案

所有iText7的文档、读写器、合并器对象都实现了IDisposable接口,使用using语句包裹是最稳妥的资源释放方式——using会在代码块执行结束时,无论是否抛出异常都自动调用对象的Dispose()方法(iText7中Dispose和Close的执行逻辑完全一致),从根源上避免资源泄漏。
修复后的完整代码如下:

private void button1_Click(object sender, EventArgs e)
{
    folderBrowserDialog1 = new FolderBrowserDialog();
    DialogResult dr = folderBrowserDialog1.ShowDialog();

    // 用户取消选择或路径为空时直接返回,避免后续逻辑异常
    if(dr != DialogResult.OK || string.IsNullOrWhiteSpace(folderBrowserDialog1.SelectedPath))
    {
        return;
    }
    
    textBox1.Text = folderBrowserDialog1.SelectedPath;
    // 原代码中root变量赋值后未使用,已移除
    
    // 直接筛选pdf后缀文件,无需循环内判断扩展名
    string[] inputFiles = Directory.GetFiles(folderBrowserDialog1.SelectedPath, "*.pdf");
    string dest = @"D:\PDF-merge";
    string outputPath = Path.Combine(dest, "merged.pdf");

    Directory.CreateDirectory(dest);

    // 所有IDisposable对象用using块包裹,自动释放资源
    using (PdfDocument targetPdfDoc = new PdfDocument(new PdfWriter(outputPath)))
    using (PdfMerger merger = new PdfMerger(targetPdfDoc))
    {
        // 开启配置:单个文件合并完成后自动关闭源文档,无需手动调用Close
        merger.SetCloseSourceDocuments(true);
        foreach (string sourcePdfPath in inputFiles)
        {
            using (PdfReader reader = new PdfReader(sourcePdfPath))
            {
                reader.SetUnethicalReading(true);
                using (PdfDocument sourcePdfDoc = new PdfDocument(reader))
                {
                    merger.Merge(sourcePdfDoc, 1, sourcePdfDoc.GetNumberOfPages());
                }
            }
        }
    } // 代码执行到此处时,所有iText相关资源会被自动释放,文件句柄全部关闭

    // 仅用于验证内存释放效果时调用,生产环境不建议频繁手动触发GC
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
}

修复点说明

  • 新增对话框选择结果判断,避免用户取消选择后进入异常逻辑
  • 使用Path.Combine拼接路径,避免硬编码转义字符问题
  • 所有iText7可释放对象全部用using块包裹,异常场景下也能保证资源释放
  • 开启SetCloseSourceDocuments(true)配置,单个源文件合并完成后立刻释放对应资源
  • 直接通过Directory.GetFiles的筛选参数获取PDF文件,减少循环内冗余判断
  • 修复原代码中未关闭输出端PdfDocument的核心泄漏点

内容的提问来源于stack exchange,提问作者Joshua B

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 22:06:24