【问题标题】:Memory leak when splitting a 10k page PDF (iTextSharp PDF API)拆分 10k 页 PDF 时的内存泄漏(iTextSharp PDF API)
【发布时间】:2016-05-20 04:02:55
【问题描述】:

我有一个略多于 10,000 页的 PDF,我试图根据分隔页将其拆分为更小的 PDF。我当前的实现效果很好,直到您开始一次将完整的 10k 页扔给它。在大约第 50 个创建 pdf(每个约 100 页)之后,它会开始显着减慢,并且在我收到 OutOfMemoryException 之前,我的内存使用量会跃升至约 2GB。我对内存管理的经验很少,但我做了很多研究。我在这里提出这个问题只是因为它对时间很敏感,所以如果我自己没有进行合理的研究,我深表歉意。

我对原始 PDF 的初步阅读:

 var pdfDictionary = PDFHelper.ParsePDFByPage(_workItem.FileName);
        //Code behind
        public static Dictionary<int, string> ParsePDFByPage(string filePath)
        {
            var retVal = new Dictionary<int, string>();

            PdfReader reader = new PdfReader(filePath);
            for (int page = 1; page <= reader.NumberOfPages; page++)
            {
                retVal.Add(page, PdfTextExtractor.GetTextFromPage(reader, page, new StructuredTextExtractionStrategy()));
            }
            reader.Close();
            reader.Dispose();

            return retVal;
        }

阅读后,我找到了哪些页面是分隔符,并为需要从原始页面拆分的每个页面范围创建了一个 HMPdf 实例(定义如下)

var pdfsToCreate= pdfDictionary.Where(x => x.Value.Contains("DELIMITER"));
var pdfList = new List<HMPdf>();
foreach (var item in pdfsToCreate) //pdfsToCreate = Dictionary<int,string> 
{
    //Parsing logic (most removed, just know that this part works fine)

    //After parsing, create new instance of HMPdf and add it to the list
    var pdf = new HMPdf(startPage, endPage, fileName);
    pdfList.Add(pdf);
}

解析后,创建 PDF

foreach (var hmpdf in pdfList)
{
    //I've tried forcing the GC to collect after every 10 pdfs created
    string error = string.Empty;
    if (!hmpdf.TryCreate(sourcePath, destinationPath, out error))
    {
        throw new Exception("Error creating new PDF - " + error);
    }
}

HMPdf 代码隐藏

public class HMPdf
{
    private string _path;
    private string _fileName;
    private PdfCopy _pdfCopy = null;
    private PdfReader _reader = null;
    private Document _sourceDocument = null;
    private PdfImportedPage _importedPage = null;
    private int _pageFrom;
    private int _pageTo;
    private FileStream _fileStream;

    public HMPdf(int pageFrom, int pageTo, string fileName)
    {
        _pageFrom = pageFrom;
        _pageTo = pageTo;
        _fileName = fileName;
    }

    public bool TryCreate(string sourcePath, string destinationPath, out string errorMessage)
    {        
        try
        {

            _reader = new PdfReader(sourcePath);
            _sourceDocument = new Document(_reader.GetPageSizeWithRotation(_pageFrom));
            _fileStream = new System.IO.FileStream(Path.Combine(destinationPath, _fileName.ToLower().Contains(".pdf") ? _fileName : _fileName + ".pdf"),
                    System.IO.FileMode.Create);
            _pdfCopy = new PdfCopy(_sourceDocument, _fileStream);
            _sourceDocument.Open();
            for (int i = _pageFrom; i <= _pageTo; i++)
            {
                _importedPage = _pdfCopy.GetImportedPage(_reader, i);
                _pdfCopy.AddPage(_importedPage);
                _importedPage = null;
            }
            return true;
        }
        catch (Exception ex)
        {
            errorMessage = ex.Message;
            return false;
        }
        finally
        {
            if (_reader != null)
            {
                _reader.Close();
                _reader.Dispose();
                _reader = null;
            }
            if (_sourceDocument != null)
            {
                _sourceDocument.Close();
                _sourceDocument.Dispose();
                _sourceDocument = null;
            }
            if (_pdfCopy != null)
            {
                _pdfCopy.Close();
                _pdfCopy.Dispose();
                _pdfCopy = null;
            }
            if (_fileStream != null)
            {
                _fileStream.Close();
                _fileStream.Dispose();
                _fileStream = null;
            }
        }
    }
}

如您所知,我正在关闭/处理所有打开的文件流、阅读器等...(对吗?)。我尝试在每创建 10 个 pdf 后强制垃圾收集器运行,但它并没有清理任何东西。我已经运行了 Telerik JustTrace,并且我对内存管理的了解很少,但有几件事很突出。首先,在几个快照之间,有 0 个已释放的对象,在最后一个快照上,pdfList 对象占用了将近 GB 的内存。

我是否遗漏了一些非常明显的东西?

很抱歉写了这么长的文章。

【问题讨论】:

    标签: c# memory-management memory-leaks garbage-collection itextsharp


    【解决方案1】:

    也许你正在证明The Dangers of the Large Object Heap...

    尝试以减少内存使用的方式改进逻辑。

    并尽可能减少变量范围。即,不要创建不必要的类变量,而是将它们设为字段变量。

    试试下面的方法,这将减少变量的范围。

        public bool TryCreate(string sourcePath, string destinationPath, out string errorMessage)
        {
            try
            {
    
                using (var _reader = new PdfReader(sourcePath))
                {
                    using (var _sourceDocument = new Document(_reader.GetPageSizeWithRotation(_pageFrom)))
                    {
                        using (var _fileStream =
                            new System.IO.FileStream(
                                Path.Combine(destinationPath, _fileName.ToLower().Contains(".pdf") ? _fileName : _fileName + ".pdf"),
                                System.IO.FileMode.Create))
                        {
                            using (_pdfCopy = new PdfCopy(_sourceDocument, _fileStream))
                            {
                                _sourceDocument.Open();
                                for (int i = _pageFrom; i <= _pageTo; i++)
                                {
                                    _importedPage = _pdfCopy.GetImportedPage(_reader, i);
                                    _pdfCopy.AddPage(_importedPage);
                                    _importedPage = null;
                                }
                            }
                        }
                    }
                }
                return true;
            }
    
        }
    

    【讨论】:

    • 光滑如黄油!我从来没有意识到声明类级变量会导致这种影响,而且我总是忘记利用美妙的“使用”语句。文章也很棒 - 内容丰富且易于阅读。谢谢!
    • @JonathanCarroll:太好了。您不必在 finally 子句中处理它们,因为使用保证在它们超出范围之前处理它们。
    • 要清楚,不要做第一块代码,做第二块。我在阅读您的第三段时感到困惑,因为它说不要使用类变量而是使用字段变量而是class "variables" are called "fields" in the c# spec。也许更新它说使用局部变量。
    猜你喜欢
    • 2014-08-24
    • 2012-06-22
    • 1970-01-01
    • 1970-01-01
    • 2013-05-10
    • 2019-01-26
    • 2013-09-16
    • 2011-10-23
    • 1970-01-01
    相关资源
    最近更新 更多