如何:启动和停止打印线程

本主题介绍如何启动和停止打印作业处理线程。

概述

若要防止打印活动阻止用户界面响应,请创建单独的线程来处理打印作业。 启动程序时启动的线程是处理用户交互产生的窗口消息的线程,因此是 UI 线程。 程序必须毫不延迟地处理这些消息,以便用户界面及时响应用户的鼠标和键盘输入。 为了使程序能够快速响应这些消息,可以在任何一条消息期间执行的处理量都是有限的。 当用户请求需要大量处理时,另一个线程必须执行该处理,以允许程序处理后续用户交互。

在单独的线程中处理数据需要程序协调用户界面线程和处理线程的操作。 例如,当处理线程正在处理数据时,main线程不得更改该数据。 管理此情况的一种方法是通过线程安全的同步对象(如信号灯、事件和互斥体)。

同时,在处理线程运行时,必须阻止某些用户交互。 在示例程序中,应用程序数据受到保护,用户交互受到限制,方法是让打印作业处理由模式进度对话框管理。 模式对话框可阻止用户与程序的main窗口交互,从而阻止用户在打印数据时更改应用程序数据。

在处理打印作业时,用户可以执行的唯一操作是取消打印作业。 此限制通过光标形状向用户发出信号。 当光标位于“ 取消 ”按钮上方时,将显示箭头光标,表示用户可以单击此按钮。 当光标位于程序窗口区域的任何其他部分上时,将显示等待光标,指示程序正忙且无法响应用户输入。

创建打印线程过程

我们建议在打印处理时包括这些功能。

  • 打印处理分为多个步骤

    可以将打印处理划分为离散处理步骤,当用户单击“ 取消 ”按钮时,可以中断这些步骤。 这很有用,因为打印处理可以包括处理器密集型操作。 将此处理分解为步骤可以防止打印处理阻止或延迟其他线程或进程。 将处理分解为逻辑步骤还可以随时完全终止打印处理,以便在打印作业完成之前结束打印作业不会留下任何孤立资源。

    这是打印步骤的示例列表。

    HRESULT PrintStep_1_StartJob (PPRINTTHREADINFO threadInfo);
    HRESULT PrintStep_2_DoOnePackage (PPRINTTHREADINFO threadInfo);
    HRESULT PrintStep_3_DoOneDoc (PPRINTTHREADINFO threadInfo);
    HRESULT PrintStep_4_DoOnePage (PPRINTTHREADINFO threadInfo);
    HRESULT PrintStep_5_ClosePackage (PPRINTTHREADINFO threadInfo);
    HRESULT PrintStep_6_CloseJob (PPRINTTHREADINFO threadInfo);
    
  • 检查步骤之间的取消事件

    当用户单击“ 取消 ”按钮时,用户界面线程会发出取消事件的信号。 处理线程必须定期检查取消事件,以了解用户单击“取消”按钮的时间。 WaitForSingleObject 语句执行此检查,它们还为其他程序提供运行的机会,以便打印作业处理不会阻止或延迟其他线程或进程。

    下面的代码示例演示了一个测试,以查看是否发生了取消事件。

    waitStatus = WaitForSingleObject (
                    threadInfo->quitEvent, 
                    stepDelay);
    if (WAIT_OBJECT_0 == waitStatus)
    {
        hr = E_ABORT;
    }
    
  • 将状态更新发送到用户界面线程

    作为每个打印处理步骤,打印处理线程将更新消息发送到打印进度对话框,以便它可以更新进度栏。 请注意,打印进度对话框在 UI 线程中运行。

    下面的代码示例演示了其中一个更新消息调用。

    // Update print status
    PostMessage (
        threadInfo->parentWindow, 
        USER_PRINT_STATUS_UPDATE, 
        0L, 
        0L);
    

启动打印线程

打印处理线程一直运行,直到 PrintThreadProc 函数返回。 以下步骤启动打印线程。

  1. 准备用于打印的数据和用户界面元素。

    在启动打印处理线程之前,必须初始化描述打印作业和用户界面元素的数据元素。 这些元素包括游标状态,以便正确显示等待游标。 还必须配置进度栏以反映打印作业的大小。 如何:从用户收集打印作业信息中详细介绍了这些准备步骤。

    下面的代码示例演示如何配置进度栏以反映用户请求的打印作业的大小。

    // Compute the number of steps in this job.
    stepCount = (((
                // One copy of a document contains
                //  one step for each page +
                //  one step for the document
                ((threadInfo->documentContent)->pages + 1) * 
                // Each copy of the document includes
                //  two steps to open and close the document
                threadInfo->copies) + 2) * 
                // Each job includes one step to start the job
                threadInfo->packages) + 1;
    // Send the total number of steps to the progress bar control.
    SendMessage (
        dlgInfo->progressBarWindow, 
        PBM_SETRANGE32, 
        0L, 
        (stepCount));
    
  2. 启动打印处理线程。

    调用 CreateThread 以启动处理线程。

    下面的代码示例启动处理线程。

    // Start the printing thread
    threadInfo->printThreadHandle = CreateThread (
                    NULL, 
                    0L, 
                    (LPTHREAD_START_ROUTINE)PrintThreadProc,
                    (LPVOID)threadInfo, 
                    0L, 
                    &threadInfo->printThreadId);
    
  3. 检查打印处理在启动时是否失败。

    如果已成功创建线程,CreateThread 将返回创建的线程的句柄。 在新线程中启动的 PrintThreadProc 函数在开始实际打印作业处理之前会检查某些条件。 如果检测到这些检查中的任何错误,PrintThreadProc 可能会返回而不处理任何打印作业数据。 UI 线程可以检查处理线程是否成功启动,方法是等待线程句柄的时间比执行初始测试所花费的时间长,但不会超过必要的时间。 当线程退出时,线程的句柄将发出信号。 代码在启动处理线程后,在短时间内检查线程的状态。 当发生超时或发出线程句柄信号时, WaitForSingleObject 函数将返回。 如果 WaitForSingleObject 函数返回 WAIT_OBJECT_0 状态,则线程提前退出,因此应关闭打印进度对话框,如以下代码示例所示。

    // Make sure the printing thread started OK
    //  by waiting to see if it is still running after
    //  a short period of time. This time delay should be
    //  long enough to know that it's running but shorter
    //  than the shortest possible print job.
    waitStatus = WaitForSingleObject (
        threadInfo->printThreadHandle, 
        THREAD_START_WAIT);
    
    // If the object is signaled, that means that the
    //  thread terminated before the timeout period elapsed.
    if (WAIT_OBJECT_0 == waitStatus)
    {
        // The thread exited, so post close messages.
        PostMessage (hDlg, USER_PRINT_CLOSING, 0L, (LPARAM)E_FAIL);
        PostMessage (hDlg, USER_PRINT_COMPLETE, 0L, (LPARAM)E_FAIL);
    }
    

停止打印线程

当用户单击打印进度对话框中的“ 取消 ”按钮时,打印线程会收到通知,以便它可以有序地停止处理打印作业。 虽然 “取消 ”按钮和 quitEvent 事件是此处理的重要方面,但必须将整个处理序列设计为成功中断。 这意味着,如果用户在序列完成之前取消序列,则序列中的步骤不得保留任何分配的资源,这些资源不会释放。

下面的代码示例演示示例程序在处理要打印的文档中的每个页面之前如何检查 quitEvent 。 如果 发出 quitEvent 信号或检测到错误,打印处理将停止。

// While no errors and the user hasn't clicked cancel...
while (((waitStatus = WaitForSingleObject (
                        threadInfo->quitEvent, 
                        stepDelay)) == WAIT_TIMEOUT) &&
        SUCCEEDED(hr))
{
    // ...print one page
    hr = PrintStep_4_DoOnePage (threadInfo);

    // Update print status
    PostMessage (
        threadInfo->parentWindow, 
        USER_PRINT_STATUS_UPDATE, 
        0L, 
        0L);
    

    if (threadInfo->currentPage < (threadInfo->documentContent)->pages)
    {
        // More pages, so continue to the next one
        threadInfo->currentPage++;
    }
    else
    {
        // Last page printed so exit loop and close
        break;
    }
}