Guia: Iniciar e Parar uma Tarefa de Impressão

Este tópico descreve como iniciar e interromper o thread de processamento do trabalho de impressão.

Visão geral

Para evitar que as atividades de impressão bloqueiem a resposta da interface do usuário, crie um thread separado para processar o trabalho de impressão. O thread que é iniciado quando o programa é iniciado, é o thread que manipula as mensagens de janela que resultam da interação do usuário e, portanto, é o thread da interface do usuário. O programa deve processar essas mensagens sem demora para que a interface do usuário responda à entrada do mouse e teclado do usuário em tempo hábil. Para que o programa seja capaz de responder a essas mensagens rapidamente, a quantidade de processamento que pode ser realizada durante qualquer mensagem é limitada. Quando uma solicitação de usuário requer processamento extensivo, um thread diferente deve executar esse processamento para permitir que a interação subsequente do usuário seja manipulada pelo programa.

O processamento de dados em um thread separado requer que o programa coordene a operação do thread da interface do usuário e o thread de processamento. Por exemplo, enquanto o thread de processamento está processando os dados, o thread principal não deve alterar esses dados. Uma maneira de gerenciar isso é por meio de objetos de sincronização thread-safe, como semáforos, eventos e mutexes.

Ao mesmo tempo, alguma interação do usuário deve ser impedida enquanto o thread de processamento está em execução. No programa de exemplo, os dados da aplicação são protegidos e a interação do usuário é limitada devido ao processamento do trabalho de impressão ser gerido pela caixa de diálogo de progresso modal. Uma caixa de diálogo modal impede que o usuário interaja com a janela principal do programa, o que impede que o usuário altere os dados do programa do aplicativo enquanto os dados são impressos.

A única ação que o usuário pode executar enquanto um trabalho de impressão está sendo processado é cancelar o trabalho de impressão. Essa restrição é sinalizada ao usuário pela forma do cursor. Quando o cursor está sobre o botão Cancelar, um cursor de seta é exibido, o que indica que o usuário pode clicar nesse botão. Quando o cursor está sobre qualquer outra parte da área da janela do programa, um cursor de espera é exibido, o que indica que o programa está ocupado e não pode responder à entrada do usuário.

Criação do Procedimento de Fio de Impressão

Recomendamos incluir esses recursos durante o processamento de impressão.

  • O processamento de impressão é dividido em etapas

    Você pode dividir o processamento de impressão em etapas de processamento discretas que podem ser interrompidas se o usuário clicar no botão Cancelar. Isso é útil porque o processamento de impressão pode incluir operações intensivas do processador. Dividir esse processamento em etapas pode impedir que o processamento de impressão bloqueie ou atrase outros threads ou processos. Dividir o processamento em etapas lógicas também torna possível encerrar o processamento de impressão de forma limpa a qualquer momento, de modo que terminar um trabalho de impressão antes que ele tenha terminado não deixará nenhum recurso órfão.

    Esta é uma lista de exemplo de etapas de impressão.

    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);
    
  • Verificar se há um evento de cancelamento entre as etapas

    Quando o utilizador clica no botão Cancelar, o segmento da interface do utilizador sinaliza o evento de cancelamento. O thread de processamento deve verificar o evento cancel periodicamente para saber quando um usuário clicou no botão Cancelar. As instruçõesWaitForSingleObject executam essa verificação e também dão a outros programas a chance de serem executados para que o processamento do trabalho de impressão não bloqueie ou atrase outros threads ou processos.

    O exemplo de código a seguir ilustra um dos testes para ver se o evento cancel ocorreu.

    waitStatus = WaitForSingleObject (
                    threadInfo->quitEvent, 
                    stepDelay);
    if (WAIT_OBJECT_0 == waitStatus)
    {
        hr = E_ABORT;
    }
    
  • Enviar atualizações de status para o thread da interface do usuário

    A cada etapa do processamento de impressão, a thread de processamento de impressão envia mensagens de atualização para a caixa de diálogo de progresso de impressão, para que esta possa atualizar a barra de progresso. Observe que a caixa de diálogo de progresso da impressão está sendo executada no thread da interface do usuário.

    O exemplo de código a seguir ilustra uma das chamadas de mensagem de atualização.

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

Iniciando o thread de impressão

O thread de processamento de impressão é executado até que a função PrintThreadProc retorne. As etapas a seguir iniciam o thread de impressão.

  1. Prepare os dados e os elementos da interface do usuário para impressão.

    Antes de iniciar o thread de processamento de impressão, você deve inicializar os elementos de dados que descrevem o trabalho de impressão e os elementos da interface do usuário. Esses elementos incluem o estado do cursor, para que o cursor de espera seja exibido adequadamente. Você também deve configurar a barra de progresso para refletir o tamanho do trabalho de impressão. Essas etapas de preparação são descritas em detalhes em Como: Coletar informações de trabalho de impressão do usuário.

    O exemplo de código a seguir mostra como configurar a barra de progresso para refletir o tamanho do trabalho de impressão que o usuário solicitou.

    // 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. Inicie o thread de processamento de impressão.

    Chame CreateThread para iniciar o thread de processamento.

    O exemplo de código a seguir inicia o thread de processamento.

    // Start the printing thread
    threadInfo->printThreadHandle = CreateThread (
                    NULL, 
                    0L, 
                    (LPTHREAD_START_ROUTINE)PrintThreadProc,
                    (LPVOID)threadInfo, 
                    0L, 
                    &threadInfo->printThreadId);
    
  3. Verifique se o processamento de impressão falhou no início.

    CreateThread retorna um identificador para o thread criado se o thread foi criado com êxito. A função PrintThreadProc que foi iniciada no novo thread verifica algumas condições antes de iniciar o processamento real do trabalho de impressão. Se detetar algum erro nessas verificações, o PrintThreadProc poderá retornar sem processar nenhum dado de trabalho de impressão. O thread da interface do usuário pode verificar se o thread de processamento foi iniciado com êxito aguardando o identificador de thread por um período de tempo maior do que o necessário para executar os testes iniciais, mas não mais do que o necessário. Quando o thread sai, o identificador para o thread torna-se sinalizado. O código verifica o estado do thread por um curto período de tempo depois que ele inicia o thread de processamento. A funçãoWaitForSingleObject retorna quando o tempo limite ocorre ou o identificador de thread é sinalizado. Se a função WaitForSingleObject retornar um estado WAIT_OBJECT_0, o thread saiu antecipadamente e, portanto, deve-se fechar a caixa de diálogo de progresso de impressão, como mostra o exemplo de código a seguir.

    // 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);
    }
    

Parando o thread de impressão

Quando o usuário clica no botão Cancelar na caixa de diálogo de progresso da impressão, o thread de impressão é notificado para que possa parar de processar o trabalho de impressão de forma ordenada. Embora o botão Cancelar e o evento quitEvent sejam aspetos importantes deste processamento, você deve projetar toda a sequência de processamento para ser interrompida com êxito. Isso significa que as etapas na sequência não devem deixar nenhum recurso alocado que não seja liberado se um usuário cancelar a sequência antes de ela ter sido concluída.

O exemplo de código a seguir mostra como o programa de exemplo verifica o quitEvent antes de processar cada página no documento que está sendo impresso. Se o quitEvent for sinalizado ou um erro for detetado, o processamento de impressão será interrompido.

// 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;
    }
}