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

如何在C#中高效暂停打印队列中待打印的作业?

打印作业即时暂停的可靠性问题与解决方案

问题背景

开发一款C#应用用于监控和管理打印作业,核心需求是打印作业刚添加到打印机队列、尚未开始打印时立即暂停。但在适配不同打印机驱动或处理大尺寸作业时,出现了暂停时机滞后的问题:部分场景下作业在被暂停前已开始打印,偶尔会显示“已暂停 - 正在打印”的状态。

已实现的核心代码

1. 打开打印机并设置变更通知

IntPtr hPrinter;
if (!OpenPrinter(printerName, out hPrinter, IntPtr.Zero))
{
    int errorCode = Marshal.GetLastWin32Error();
    Log($"Failed to open printer: {printerName}. Error code: {errorCode}");
    return;
}

IntPtr hChange = FindFirstPrinterChangeNotification(hPrinter, PRINTER_CHANGE_ADD_JOB | PRINTER_CHANGE_WRITE_JOB | PRINTER_CHANGE_SET_JOB, 0, IntPtr.Zero);
if (hChange == IntPtr.Zero)
{
    int errorCode = Marshal.GetLastWin32Error();
    Log($"Failed to create change notification for printer: {printerName}. Error code: {errorCode}");
    ClosePrinter(hPrinter);
    return;
}

2. 监控打印机变更

while (bKeepMonitoring && !cancellationToken.IsCancellationRequested)
{
    uint waitResult = WaitForSingleObject(hChange, 1000);
    if (waitResult == WAIT_OBJECT_0)
    {
        int pdwChange;
        IntPtr lppPrinterNotifyInfo;
        bool fcnreturn = FindNextPrinterChangeNotification(hChange, out pdwChange, IntPtr.Zero, out lppPrinterNotifyInfo);

        if (fcnreturn)
        {
            if ((pdwChange & PRINTER_CHANGE_ADD_JOB) != 0)
            {
                Log($"A new print job was added to printer: {printerName}");
                JOB_INFO_1? jobInfo = PausePrintJob(hPrinter);
                if (jobInfo != null)
                {
                    Log($"Job paused: {jobInfo.Value.JobId}, Document: {jobInfo.Value.pDocument}");
                }
            }
            // Additional handling for other events
        }
        else
        {
            int errorCode = Marshal.GetLastWin32Error();
            Log($"Failed to get next printer change notification for printer: {printerName}. Error code: {errorCode}");
            bKeepMonitoring = false;
        }
    }
    else if (waitResult == WAIT_TIMEOUT)
    {
        Log("Timeout...");
    }
    else
    {
        int errorCode = Marshal.GetLastWin32Error();
        Log($"WaitForSingleObject failed for printer: {printerName}. Error code: {errorCode}");
        bKeepMonitoring = false;
    }
}

3. 暂停打印作业

private static JOB_INFO_1? PausePrintJob(IntPtr hPrinter)
{
    uint needed = 0, returned = 0;
    IntPtr buffer = IntPtr.Zero;

    try
    {
        EnumJobs(hPrinter, 0, 1, 1, buffer, 0, out needed, out returned);
        if (needed == 0)
        {
            Log("No job to pause.");
            return null;
        }

        buffer = Marshal.AllocHGlobal((int)needed);
        if (EnumJobs(hPrinter, 0, 1, 1, buffer, needed, out needed, out returned) && returned > 0)
        {
            JOB_INFO_1 jobInfo = (JOB_INFO_1)Marshal.PtrToStructure(buffer, typeof(JOB_INFO_1));
            Log($"Pausing job: {jobInfo.JobId}, Document: {jobInfo.pDocument}");

            if (!SetJob(hPrinter, jobInfo.JobId, 0, IntPtr.Zero, JOB_CONTROL_PAUSE))
            {
                int errorCode = Marshal.GetLastWin32Error();
                Log($"Failed to pause job: {jobInfo.JobId}. Error code: {errorCode}");
            }
            else
            {
                return jobInfo;
            }
        }
    }
    finally
    {
        if (buffer != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(buffer);
        }
    }

    return null;
}

核心疑问

  1. 如何确保作业一添加到队列就立即被暂停?
  2. 是否存在更适合此类作业控制的特定打印机驱动或配置?
  3. 如何提升检测和暂停打印作业的时机准确性与可靠性?

补充信息

  • 测试使用“Generic / Text Only”打印机驱动
  • 已尝试调整WaitForSingleObject的不同超时值
  • 应用程序以管理员权限运行

解决方案建议

针对即时暂停的优化

  • 替换滞后的通知事件:PRINTER_CHANGE_ADD_JOB是作业完全加入队列后的通知,部分驱动可能在通知触发前就开始调度作业。改用PRINTER_CHANGE_JOB_CREATED(需确认系统支持),或监控PRINTER_CHANGE_ALL并过滤作业状态为JOB_STATUS_SPOOLING的事件——此时作业正在写入队列,还未进入打印调度。
  • 直接从通知中提取作业ID:避免依赖EnumJobs(0,1,...)获取最新作业(可能拿到队列中最旧的作业),直接解析FindNextPrinterChangeNotification返回的lppPrinterNotifyInfo数据,提取新作业ID并执行暂停,减少延迟:
    if (lppPrinterNotifyInfo != IntPtr.Zero)
    {
        PRINTER_NOTIFY_INFO notifyInfo = (PRINTER_NOTIFY_INFO)Marshal.PtrToStructure(lppPrinterNotifyInfo, typeof(PRINTER_NOTIFY_INFO));
        for (int i = 0; i < notifyInfo.Count; i++)
        {
            PRINTER_NOTIFY_INFO_DATA data = (PRINTER_NOTIFY_INFO_DATA)Marshal.PtrToStructure(
                IntPtr.Add(lppPrinterNotifyInfo, Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO)) + i * Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO_DATA))),
                typeof(PRINTER_NOTIFY_INFO_DATA));
            if (data.Field == PRINTER_NOTIFY_FIELD_JOB_ID && data.Type == PRINTER_NOTIFY_TYPE_JOB)
            {
                uint jobId = (uint)Marshal.ReadInt32(data.NotifyData.Data.pBuf);
                SetJob(hPrinter, jobId, 0, IntPtr.Zero, JOB_CONTROL_PAUSE);
            }
        }
        Marshal.FreeHGlobal(lppPrinterNotifyInfo);
    }
    
  • 提前锁定打印机队列:监控开始时调用SetPrinter将打印机设置为暂停状态,待新作业加入并暂停后再恢复打印机,从根源阻止作业开始打印(需注意处理队列中已有的作业)。

打印机驱动与配置优化

  • 优先使用厂商原生驱动:相较于“Generic / Text Only”驱动,厂商提供的PCL(Printer Command Language)或PS(PostScript)驱动对作业状态的反馈更及时,支持更细粒度的控制API。
  • 调整端口与调度设置:使用本地端口(如FILE:)而非网络端口,减少网络延迟;若用网络打印机,在服务器端设置“先暂停新作业”的调度优先级。
  • 禁用“立即打印”功能:部分驱动的“立即处理作业”选项会在作业未完全写入队列时启动打印,需在打印机属性中关闭该功能。

可靠性提升

  • 多事件联动监控:同时监听PRINTER_CHANGE_ADD_JOB、PRINTER_CHANGE_JOB_STATUS和PRINTER_CHANGE_WRITE_JOB事件,即使某一通知延迟,也能通过其他事件触发暂停。
  • 添加重试机制:若首次暂停失败(如返回ERROR_ACCESS_DENIED或ERROR_JOB_NOT_FOUND),在100ms内重试2-3次,避免因作业状态未同步导致的操作失败。
  • 实时状态校验:暂停操作后立即调用GetJob获取作业状态,若仍处于JOB_STATUS_PRINTING,则再次执行暂停并记录日志,用于后续排查。

内容的提问来源于stack exchange,提问作者Çağatay Kaya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:50:12