WinForms .NET4.8中打印至锁定文件触发异常后程序挂起的解决办法
打印至锁定文件后程序挂起的解决办法
问题场景
在.NET Framework 4.8的WinForms项目里,用PrintDocument.Print()打印到文件(比如PDF)时,如果用户通过类SaveFileDialog窗口选了已被其他进程锁定的文件,会抛出Win32Exception。虽然能捕获异常弹出错误对话框,但用户点确认后程序直接挂起。
试过两种无效方案:
- 把
Print()放到BackgroundWorker里,结果因为跨线程访问UI对象失败 - 自定义
PrintController,没法保持原来的打印质量
解决思路
方案1:提前拦截文件选择,先验证文件可用性
不让系统弹出默认的打印到文件对话框,自己处理文件选择和可用性检查,从根上避免异常:
- 关掉
PrintDialog的AllowPrintToFile选项,手动处理打印到文件的逻辑 - 自己弹
SaveFileDialog让用户选目标文件 - 尝试用独占模式打开文件,验证是否可写入(未被锁定)
- 验证通过后,设置
PrinterSettings.PrintFileName再执行打印
方案2:异常后强制清理打印线程(反射实现)
要是必须保留系统默认对话框,捕获异常后通过反射拿到PrintDocument内部的打印线程资源,强制清理:
- 抓到
Win32Exception后,用反射获取PrintDocument的_printController字段 - 再拿到打印控制器内部的后台线程,强制终止
- 必要时可配合Unsafe调用Win32函数清理残留句柄
方案1代码实现
private void buttonPrint_Click(object sender, EventArgs e) { try { Directory.SetCurrentDirectory(System.IO.Path.GetDirectoryName(Application.ExecutablePath)); PrintDocument document = this.printPreviewControl1.Document; if (askAsPrinterBodelBeforPrint) { printDialog1.AllowCurrentPage = true; printDialog1.AllowPrintToFile = false; // 禁用系统默认的打印到文件选项 printDialog1.AllowSelection = true; printDialog1.AllowSomePages = true; printDialog1.PrinterSettings = document.PrinterSettings; if (printDialog1.ShowDialog(this) != DialogResult.OK) return; } // 调整打印范围 if (document.PrinterSettings.PrintRange == PrintRange.CurrentPage || document.PrinterSettings.PrintRange == PrintRange.Selection) { document.PrinterSettings.PrintRange = PrintRange.SomePages; document.PrinterSettings.FromPage = this.printPreviewControl1.StartPage + 1; document.PrinterSettings.ToPage = this.printPreviewControl1.StartPage + 1; } // 手动处理打印到文件逻辑 if (document.PrinterSettings.PrintToFile) { using (SaveFileDialog saveFileDialog = new SaveFileDialog()) { saveFileDialog.Filter = "PDF 文件(*.pdf)|*.pdf|所有文件(*.*)|*.*"; saveFileDialog.Title = "选择打印输出文件"; if (saveFileDialog.ShowDialog(this) != DialogResult.OK) return; // 检查文件是否可写入(未被锁定) bool isFileAvailable = false; FileStream fs = null; try { // 用独占模式打开,验证文件是否被占用 fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write, FileShare.None); isFileAvailable = true; } catch (IOException) { MyKryptonMessageBox.Show(lang.ReadString(this.Name, "FileLockedError", "文件已被锁定,请选择其他文件!")); return; } finally { fs?.Close(); } // 设置打印目标文件名 document.PrinterSettings.PrintFileName = saveFileDialog.FileName; } } // 执行打印 document.Print(); if (!frmPrintOptions.ClosePreview) DialogResult = DialogResult.OK; } catch (Exception ee) { Logger.LogError("frmPrintPrewiew:buttonPrint_Click:" + ee.Message); Logger.LogError("frmPrintPrewiew:buttonPrint_Click:" + ee.StackTrace); Logger.LogInfo("工作目录: " + Directory.GetCurrentDirectory()); MyKryptonMessageBox.Show(lang.ReadString(this.Name, "PrintError", "打印出错!")); // 可选:如果还是触发异常,调用清理方法终止打印线程 CleanupPrintThread(this.printPreviewControl1.Document); } } // 方案2的线程清理方法(反射实现) private void CleanupPrintThread(PrintDocument document) { try { // 获取PrintDocument内部的_printController字段 FieldInfo printControllerField = typeof(PrintDocument).GetField("_printController", BindingFlags.NonPublic | BindingFlags.Instance); if (printControllerField == null) return; PrintController printController = (PrintController)printControllerField.GetValue(document); if (printController is StandardPrintController standardController) { // 获取StandardPrintController内部的_thread字段 FieldInfo threadField = typeof(StandardPrintController).GetField("_thread", BindingFlags.NonPublic | BindingFlags.Instance); if (threadField == null) return; Thread printThread = (Thread)threadField.GetValue(standardController); if (printThread != null && printThread.IsAlive) { // 强制终止打印线程(可能有轻微资源泄漏,但能解决挂起问题) printThread.Abort(); } } } catch (Exception ex) { Logger.LogError("清理打印线程失败: " + ex.Message); } }
内容的提问来源于stack exchange,提问作者Breyer
相关产品推荐
相关产品推荐

