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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 03:07:56