PdfDocument 在关闭后保持锁定状态
PdfDocument remains locked after closing
我有一项 windows 服务,可以即时合并 PDF,然后将它们移动到另一个位置。在大多数情况下,我无法控制某人想要合并的内容。碰巧经常会处理损坏的 PDF,因此创建新的 PdfDocument 会抛出 PdfException "Trailer not found"。我正在捕获异常并关闭文档,但它在关闭后出现 PDF 本身仍然以某种方式被锁定。我需要删除该目录,但在尝试这样做时它会抛出 IOException 并使服务崩溃。
我已验证调用 PdfDocument 构造函数是锁定 pdf 的原因,并且在关闭文件后立即保持锁定状态。
有什么想法吗? iText 可以做些什么来帮助解决这个问题,还是我需要想出一些办法来预先检查损坏的 PDF?
进程目录
private void ProcessDirectory(string directoryPath)
{
EventLogManager.WriteInformation("ProcessDirectory");
// DON'T TOUCH THE BACKUPS, ERRORS AND WORK DIRECTORIES. Just in case they were made or renamed after the fact for some reason
if (directoryPath != this._errorsPath && directoryPath != this._backupsPath && directoryPath != this._workPath)
{
string pdfJsonPath = System.IO.Path.Combine(directoryPath, "pdf.json");
if (File.Exists(pdfJsonPath))
{
string workPath = System.IO.Path.Combine(this._workPath, System.IO.Path.GetFileName(directoryPath));
try
{
CopyToDirectory(directoryPath, workPath);
PdfMerge pdfMerge = null;
string jsonPath = System.IO.Path.Combine(workPath, "pdf.json");
using (StreamReader r = Helpers.GetStreamReader(jsonPath))
{
string json = r.ReadToEnd();
pdfMerge = JsonConvert.DeserializeObject<PdfMerge>(json);
}
FillFormFields(workPath, pdfMerge);
if (pdfMerge.Pdfs.Any(p => !String.IsNullOrWhiteSpace(p.OverlayFilename)))
{
ApplyOverlays(workPath, pdfMerge);
}
MergePdfs(workPath, pdfMerge);
//NumberPages(workPath, pdfMerge);
FinishPdf(workPath, pdfMerge);
// Move original to backups directory
if (DoSaveBackups)
{
string backupsPath = System.IO.Path.Combine(this._backupsPath, String.Format("{0}_{1}", System.IO.Path.GetFileName(directoryPath), DateTime.Now.ToString("yyyyMMddHHmmss")));
Directory.Move(directoryPath, backupsPath);
}
else
{
Directory.Delete(directoryPath, true);
}
}
catch (Exception ex)
{
EventLogManager.WriteError(ex);
if (DoSaveErrors)
{
// Move original to errors directory
string errorsPath = System.IO.Path.Combine(this._errorsPath, String.Format("{0}_{1}", System.IO.Path.GetFileName(directoryPath), DateTime.Now.ToString("yyyyMMddHHmmss")));
Directory.Move(directoryPath, errorsPath);
}
else
{
Directory.Delete(directoryPath, true);
}
}
// Delete work directory
// THIS IS WHERE THE IOEXCEPTION OCCURS AND THE SERVICE CRASHES
Directory.Delete(workPath, true);
}
else
{
EventLogManager.WriteInformation(String.Format("No pdf.json file. {0} skipped.", directoryPath));
}
}
}
FillFormFields
private void FillFormFields(string directoryPath, PdfMerge pdfMerge)
{
if (pdfMerge != null && pdfMerge.Pdfs != null)
{
string formPath = String.Empty;
string newFilePath;
PdfDocument document = null;
PdfAcroForm form;
PdfFormField pdfFormField;
foreach (var pdf in pdfMerge.Pdfs)
{
try
{
formPath = System.IO.Path.Combine(directoryPath, pdf.Filename);
newFilePath = System.IO.Path.Combine(
directoryPath,
String.Format("{0}{1}", String.Format("{0}{1}", System.IO.Path.GetFileNameWithoutExtension(pdf.Filename), "_Revised"), System.IO.Path.GetExtension(pdf.Filename)));
// THIS IS WHERE THE PDFEXCEPTOIN OCCURS
document = new PdfDocument(Helpers.GetPdfReader(formPath), new PdfWriter(newFilePath));
form = PdfAcroForm.GetAcroForm(document, true);
if (pdf.Fields != null && pdf.Fields.Count > 0)
{
foreach (var field in pdf.Fields)
{
if (field.Value != null)
{
pdfFormField = form.GetField(field.Name);
if (pdfFormField != null)
{
form.GetField(field.Name).SetValue(field.Value);
}
else
{
EventLogManager.WriteWarning(String.Format("Field '{0}' does not exist in '{1}'", field.Name, pdf.Filename));
}
}
}
}
form.FlattenFields();
}
catch (Exception ex)
{
throw new Exception(String.Format("An exception occurred filling form fields for {0}", pdf.Filename), ex);
}
finally
{
if (document != null)
{
document.Close();
}
}
// Now rename the new one back to the old name
File.Delete(formPath);
File.Move(newFilePath, formPath);
}
}
}
更新
似乎为了正确处理所有内容,您必须将单独的 PdfReader 和 PdfWriter 对象声明为 using 语句并将它们传递到 PdfDocument。像这样:
using (reader = Helpers.GetPdfReader(formPath))
{
using (writer = new PdfWriter(newFilePath))
{
using (document = new PdfDocument(reader, writer))
{
// The rest of the code here
}
}
}
我不确定为什么除了 iText 在处理 PdfDocument 时没有处理单个 PdfReader 和 PdfWriter 之外,我认为它会处理。
找出哪些 itext7 类 实现了 IDisposable(来自文档或 Visual Studio 对象浏览器等),并确保你在 using 块中使用它们,就像你已经使用的一样有 StreamReader 的使用块。
编辑:@sourkrause 的解决方案可以缩短为:
using (reader = Helpers.GetPdfReader(formPath))
using (writer = new PdfWriter(newFilePath))
using (document = new PdfDocument(reader, writer))
{
// The rest of the code here
}
我知道这是一个老问题,但这是我在 iText7 中解决问题的方法,与公认的答案完全不同。由于我无法使用 using 语句,因此在关闭文档时我采用了不同的方法。这可能看起来有点过头了,但效果很好。
首先我关闭了文档:
Document.Close();
这里没有什么不寻常的。但是,在这样做之后,我关闭/处理了 Reader 和 Writer 实例。关闭它们后,我将按顺序将编写器 reader 和文档设置为空。 GC 应该负责清理这些,但对于我的使用来说,持有这些实例的对象仍在使用中,所以为了释放一些内存,我正在执行这个额外的步骤。
第 2 步
Writer.Close();
Writer.Dispose();
Writer = null;
第 3 步
Reader.SetCloseStream(true);
Reader.Close();
Reader = null;
第 4 步
Document = null;
我建议您将每个步骤都包装在一个 try catch 中;根据您的代码 运行,您可能会同时看到所有这些问题。
我认为这里最重要的部分是对 reader 采取的行动。出于某种原因,reader 默认情况下调用 .Close() 时似乎不会关闭流。
***虽然 运行 在生产中,但我仍然注意到一个文件(到目前为止)在关闭后立即尝试删除时仍然持有锁。我添加了一个等待几秒钟再试的捕获器。这似乎可以解决那些更“顽固”的文件。
我有一项 windows 服务,可以即时合并 PDF,然后将它们移动到另一个位置。在大多数情况下,我无法控制某人想要合并的内容。碰巧经常会处理损坏的 PDF,因此创建新的 PdfDocument 会抛出 PdfException "Trailer not found"。我正在捕获异常并关闭文档,但它在关闭后出现 PDF 本身仍然以某种方式被锁定。我需要删除该目录,但在尝试这样做时它会抛出 IOException 并使服务崩溃。
我已验证调用 PdfDocument 构造函数是锁定 pdf 的原因,并且在关闭文件后立即保持锁定状态。
有什么想法吗? iText 可以做些什么来帮助解决这个问题,还是我需要想出一些办法来预先检查损坏的 PDF?
进程目录
private void ProcessDirectory(string directoryPath)
{
EventLogManager.WriteInformation("ProcessDirectory");
// DON'T TOUCH THE BACKUPS, ERRORS AND WORK DIRECTORIES. Just in case they were made or renamed after the fact for some reason
if (directoryPath != this._errorsPath && directoryPath != this._backupsPath && directoryPath != this._workPath)
{
string pdfJsonPath = System.IO.Path.Combine(directoryPath, "pdf.json");
if (File.Exists(pdfJsonPath))
{
string workPath = System.IO.Path.Combine(this._workPath, System.IO.Path.GetFileName(directoryPath));
try
{
CopyToDirectory(directoryPath, workPath);
PdfMerge pdfMerge = null;
string jsonPath = System.IO.Path.Combine(workPath, "pdf.json");
using (StreamReader r = Helpers.GetStreamReader(jsonPath))
{
string json = r.ReadToEnd();
pdfMerge = JsonConvert.DeserializeObject<PdfMerge>(json);
}
FillFormFields(workPath, pdfMerge);
if (pdfMerge.Pdfs.Any(p => !String.IsNullOrWhiteSpace(p.OverlayFilename)))
{
ApplyOverlays(workPath, pdfMerge);
}
MergePdfs(workPath, pdfMerge);
//NumberPages(workPath, pdfMerge);
FinishPdf(workPath, pdfMerge);
// Move original to backups directory
if (DoSaveBackups)
{
string backupsPath = System.IO.Path.Combine(this._backupsPath, String.Format("{0}_{1}", System.IO.Path.GetFileName(directoryPath), DateTime.Now.ToString("yyyyMMddHHmmss")));
Directory.Move(directoryPath, backupsPath);
}
else
{
Directory.Delete(directoryPath, true);
}
}
catch (Exception ex)
{
EventLogManager.WriteError(ex);
if (DoSaveErrors)
{
// Move original to errors directory
string errorsPath = System.IO.Path.Combine(this._errorsPath, String.Format("{0}_{1}", System.IO.Path.GetFileName(directoryPath), DateTime.Now.ToString("yyyyMMddHHmmss")));
Directory.Move(directoryPath, errorsPath);
}
else
{
Directory.Delete(directoryPath, true);
}
}
// Delete work directory
// THIS IS WHERE THE IOEXCEPTION OCCURS AND THE SERVICE CRASHES
Directory.Delete(workPath, true);
}
else
{
EventLogManager.WriteInformation(String.Format("No pdf.json file. {0} skipped.", directoryPath));
}
}
}
FillFormFields
private void FillFormFields(string directoryPath, PdfMerge pdfMerge)
{
if (pdfMerge != null && pdfMerge.Pdfs != null)
{
string formPath = String.Empty;
string newFilePath;
PdfDocument document = null;
PdfAcroForm form;
PdfFormField pdfFormField;
foreach (var pdf in pdfMerge.Pdfs)
{
try
{
formPath = System.IO.Path.Combine(directoryPath, pdf.Filename);
newFilePath = System.IO.Path.Combine(
directoryPath,
String.Format("{0}{1}", String.Format("{0}{1}", System.IO.Path.GetFileNameWithoutExtension(pdf.Filename), "_Revised"), System.IO.Path.GetExtension(pdf.Filename)));
// THIS IS WHERE THE PDFEXCEPTOIN OCCURS
document = new PdfDocument(Helpers.GetPdfReader(formPath), new PdfWriter(newFilePath));
form = PdfAcroForm.GetAcroForm(document, true);
if (pdf.Fields != null && pdf.Fields.Count > 0)
{
foreach (var field in pdf.Fields)
{
if (field.Value != null)
{
pdfFormField = form.GetField(field.Name);
if (pdfFormField != null)
{
form.GetField(field.Name).SetValue(field.Value);
}
else
{
EventLogManager.WriteWarning(String.Format("Field '{0}' does not exist in '{1}'", field.Name, pdf.Filename));
}
}
}
}
form.FlattenFields();
}
catch (Exception ex)
{
throw new Exception(String.Format("An exception occurred filling form fields for {0}", pdf.Filename), ex);
}
finally
{
if (document != null)
{
document.Close();
}
}
// Now rename the new one back to the old name
File.Delete(formPath);
File.Move(newFilePath, formPath);
}
}
}
更新
似乎为了正确处理所有内容,您必须将单独的 PdfReader 和 PdfWriter 对象声明为 using 语句并将它们传递到 PdfDocument。像这样:
using (reader = Helpers.GetPdfReader(formPath))
{
using (writer = new PdfWriter(newFilePath))
{
using (document = new PdfDocument(reader, writer))
{
// The rest of the code here
}
}
}
我不确定为什么除了 iText 在处理 PdfDocument 时没有处理单个 PdfReader 和 PdfWriter 之外,我认为它会处理。
找出哪些 itext7 类 实现了 IDisposable(来自文档或 Visual Studio 对象浏览器等),并确保你在 using 块中使用它们,就像你已经使用的一样有 StreamReader 的使用块。
编辑:@sourkrause 的解决方案可以缩短为:
using (reader = Helpers.GetPdfReader(formPath))
using (writer = new PdfWriter(newFilePath))
using (document = new PdfDocument(reader, writer))
{
// The rest of the code here
}
我知道这是一个老问题,但这是我在 iText7 中解决问题的方法,与公认的答案完全不同。由于我无法使用 using 语句,因此在关闭文档时我采用了不同的方法。这可能看起来有点过头了,但效果很好。
首先我关闭了文档:
Document.Close();
这里没有什么不寻常的。但是,在这样做之后,我关闭/处理了 Reader 和 Writer 实例。关闭它们后,我将按顺序将编写器 reader 和文档设置为空。 GC 应该负责清理这些,但对于我的使用来说,持有这些实例的对象仍在使用中,所以为了释放一些内存,我正在执行这个额外的步骤。
第 2 步
Writer.Close();
Writer.Dispose();
Writer = null;
第 3 步
Reader.SetCloseStream(true);
Reader.Close();
Reader = null;
第 4 步
Document = null;
我建议您将每个步骤都包装在一个 try catch 中;根据您的代码 运行,您可能会同时看到所有这些问题。
我认为这里最重要的部分是对 reader 采取的行动。出于某种原因,reader 默认情况下调用 .Close() 时似乎不会关闭流。
***虽然 运行 在生产中,但我仍然注意到一个文件(到目前为止)在关闭后立即尝试删除时仍然持有锁。我添加了一个等待几秒钟再试的捕获器。这似乎可以解决那些更“顽固”的文件。