如何在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; }
核心疑问
- 如何确保作业一添加到队列就立即被暂停?
- 是否存在更适合此类作业控制的特定打印机驱动或配置?
- 如何提升检测和暂停打印作业的时机准确性与可靠性?
补充信息
- 测试使用“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
相关产品推荐
相关产品推荐

