Compartilhar via


Instruções passo a passo: removendo trabalho de um thread de interface de usuário

Este documento demonstra como usar o tempo de execução de simultaneidade para mover o trabalho executado por thread de (UI) da interface do usuário em um aplicativo (MFC) de classes do Microsoft a um thread de trabalho. Este documento também demonstra como melhorar o desempenho de uma operação demorada de desenho.

Removendo o trabalho do thread de interface do usuário descarregando operações de bloqueio, por exemplo, desenhar, a threads de trabalho pode melhorar a capacidade de resposta de seu aplicativo. Este passo a passo usa uma rotina de desenho que gerencia o fractal de Mandelbrot para demonstrar uma operação demorada de bloqueio. A geração de fractal de Mandelbrot também é uma boa candidata para o parallelization porque a computação de cada x é independente de todas computações restantes.

Pré-requisitos

Leia os tópicos a seguir antes de começar este passo a passo:

Recomendamos também incluem os fundamentos de desenvolvimento de aplicativos e MFC GDI+ antes de começar este passo a passo. Para obter mais informações sobre MFC, consulte Aplicativos para desktop do MFC. Para obter mais informações sobre como GDI+, consulte GDI+.

Seções

Essa explicação passo a passo contém as seguintes seções:

  • Criando o aplicativo MFC

  • Implementando a versão do aplicativo de Mandelbrot serial

  • Removendo o trabalho do thread da interface do usuário

  • Aprimorando o desempenho de desenho

  • Adicionando suporte para o cancelamento

Criando o aplicativo MFC

Esta seção descreve como criar o aplicativo básico de MFC.

Para criar um aplicativo de MFC do Visual C++

  1. No menu Arquivo, clique em Novo e em Projeto.

  2. Na caixa de diálogo de Novo Projeto , no painel de Modelos Instalados , **Visual C++**selecione e, em seguida, no painel de Modelos , Aplicativo do MFCselecione. Digite um nome para o projeto, por exemplo, Mandelbrot, e clique em OK para exibir Assistente para Aplicativo do MFC.

  3. No painel de Tipo de Aplicativo , Único documentoselecione. Verifique se a caixa de seleção de Suporte à arquitetura do documento/exibição está desmarcada.

  4. Clique Concluir para criar o projeto e fechar Assistente para Aplicativo do MFC.

    Verifique se o aplicativo foi criado com êxito criando e executando o. Para criar o aplicativo de Compilar , no menu, clique em Compilar solução. Se o aplicativo compila com êxito, execute o aplicativo Iniciar Depuração clicando em no menu de Depurar .

Implementando a versão do aplicativo de Mandelbrot serial

Esta seção descreve como desenhar o fractal de Mandelbrot. Esta versão desenha o fractal de Mandelbrot a um objeto de GDI+Bitmap e copia o conteúdo desse bitmap para a janela do cliente.

Para implementar a versão do aplicativo de Mandelbrot serial

  1. Em stdafx.h, adicione a seguinte política de #include :

    #include <memory>
    
  2. Em ChildView.h, depois da política de pragma , defina o tipo de BitmapPtr . O tipo de BitmapPtr habilita um ponteiro para um objeto de Bitmap a ser compartilhado por vários componentes do. O objeto de Bitmap será excluído quando não é referenciado por qualquer componente.

    typedef std::shared_ptr<Gdiplus::Bitmap> BitmapPtr;
    
  3. Em ChildView.h, adicione o seguinte código na seção de protected da classe de CChildView :

    protected:
       // Draws the Mandelbrot fractal to the specified Bitmap object.
       void DrawMandelbrot(BitmapPtr);
    
    protected:
       ULONG_PTR m_gdiplusToken;
    
  4. Em ChildView.cpp, faça comentários para fora ou remova as seguintes linhas.

    //#ifdef _DEBUG 
    //#define new DEBUG_NEW 
    //#endif
    

    Em construções de depuração, essa etapa impedirá que o aplicativo use o alocador de DEBUG_NEW , que é incompatível com GDI+.

  5. Em ChildView.cpp, adicionar uma política de using ao namespace de Gdiplus .

    using namespace Gdiplus;
    
  6. Adicione o seguinte código para o construtor e o destruidor da classe de CChildView para inicializar e fechar GDI+.

    CChildView::CChildView()
    {
       // Initialize GDI+.
       GdiplusStartupInput gdiplusStartupInput;
       GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
    }
    
    CChildView::~CChildView()
    {
       // Shutdown GDI+.
       GdiplusShutdown(m_gdiplusToken);
    }
    
  7. Implemente o método CChildView::DrawMandelbrot. Esse método desenha o fractal de Mandelbrot ao objeto especificado de Bitmap .

    // Draws the Mandelbrot fractal to the specified Bitmap object. 
    void CChildView::DrawMandelbrot(BitmapPtr pBitmap)
    {
       if (pBitmap == NULL)
          return;
    
       // Get the size of the bitmap. 
       const UINT width = pBitmap->GetWidth();
       const UINT height = pBitmap->GetHeight();
    
       // Return if either width or height is zero. 
       if (width == 0 || height == 0)
          return;
    
       // Lock the bitmap into system memory.
       BitmapData bitmapData;   
       Rect rectBmp(0, 0, width, height);
       pBitmap->LockBits(&rectBmp, ImageLockModeWrite, PixelFormat32bppRGB, 
          &bitmapData);
    
       // Obtain a pointer to the bitmap bits. 
       int* bits = reinterpret_cast<int*>(bitmapData.Scan0);
    
       // Real and imaginary bounds of the complex plane. 
       double re_min = -2.1;
       double re_max = 1.0;
       double im_min = -1.3;
       double im_max = 1.3;
    
       // Factors for mapping from image coordinates to coordinates on the complex plane. 
       double re_factor = (re_max - re_min) / (width - 1);
       double im_factor = (im_max - im_min) / (height - 1);
    
       // The maximum number of iterations to perform on each point. 
       const UINT max_iterations = 1000;
    
       // Compute whether each point lies in the Mandelbrot set. 
       for (UINT row = 0u; row < height; ++row)
       {
          // Obtain a pointer to the bitmap bits for the current row. 
          int *destPixel = bits + (row * width);
    
          // Convert from image coordinate to coordinate on the complex plane. 
          double y0 = im_max - (row * im_factor);
    
          for (UINT col = 0u; col < width; ++col)
          {
             // Convert from image coordinate to coordinate on the complex plane. 
             double x0 = re_min + col * re_factor;
    
             double x = x0;
             double y = y0;
    
             UINT iter = 0;
             double x_sq, y_sq;
             while (iter < max_iterations && ((x_sq = x*x) + (y_sq = y*y) < 4))
             {
                double temp = x_sq - y_sq + x0;
                y = 2 * x * y + y0;
                x = temp;
                ++iter;
             }
    
             // If the point is in the set (or approximately close to it), color 
             // the pixel black. 
             if(iter == max_iterations) 
             {         
                *destPixel = 0;
             }
             // Otherwise, select a color that is based on the current iteration. 
             else
             {
                BYTE red = static_cast<BYTE>((iter % 64) * 4);
                *destPixel = red<<16;
             }
    
             // Move to the next point.
             ++destPixel;
          }
       }
    
       // Unlock the bitmap from system memory.
       pBitmap->UnlockBits(&bitmapData);
    }
    
  8. Implemente o método CChildView::OnPaint. Esse método chama CChildView::DrawMandelbrot e copia o conteúdo do objeto de Bitmap à janela.

    void CChildView::OnPaint() 
    {
       CPaintDC dc(this); // device context for painting 
    
       // Get the size of the client area of the window.
       RECT rc;
       GetClientRect(&rc);
    
       // Create a Bitmap object that has the width and height of  
       // the client area.
       BitmapPtr pBitmap(new Bitmap(rc.right, rc.bottom));
    
       if (pBitmap != NULL)
       {
          // Draw the Mandelbrot fractal to the bitmap.
          DrawMandelbrot(pBitmap);
    
          // Draw the bitmap to the client area.
          Graphics g(dc);
          g.DrawImage(pBitmap.get(), 0, 0);
       }
    }
    
  9. Verifique se o aplicativo seja atualizado com êxito criando e executando o.

A ilustração a seguir mostra os resultados do aplicativo de Mandelbrot.

O aplicativo Mandelbrot

Como a computação para cada x é computacionalmente dispendiosa, o thread de interface do usuário não pode processar mensagens adicionais até que a computação total seja concluída. Isso pode diminuir a capacidade de resposta no aplicativo. Porém, você pode reduzir esse problema removendo o trabalho do thread de interface do usuário.

[Superior]

Removendo o trabalho do thread de interface do usuário

Esta seção mostra como remover o trabalho de desenho do thread de interface de usuário no aplicativo de Mandelbrot. Movendo o trabalho de desenho do thread de interface do usuário a um thread de trabalho, o thread de interface do usuário pode processar mensagens enquanto o thread de trabalho gerencia a imagem em segundo plano.

O tempo de execução de simultaneidade fornece três maneiras de executar tarefas: grupos de trabalho, agentes assíncronas, e tarefas de peso leve. Embora você possa usar qualquer um desses mecanismos para remover o trabalho do thread de interface do usuário, esse exemplo usa um objeto de concurrency::task_group porque os grupos de trabalho dão suporte ao cancelamento. Este passo a passo usa posteriormente em cancelar para reduzir a quantidade de trabalho executado quando a janela do cliente é redimensionada, e para executar a limpeza quando a janela é destruída.

Este exemplo também usa um objeto de concurrency::unbounded_buffer para habilitar o thread de interface do usuário e o thread de trabalho para comunicação entre si. Depois que o thread de trabalho gerencia a imagem, envia um ponteiro para o objeto de Bitmap ao objeto de unbounded_buffer e então envia uma mensagem de pintura para o thread de interface do usuário. O thread de interface do usuário pega do objeto de unbounded_buffer o objeto de Bitmap e desenhar-lo à janela do cliente.

Para remover o trabalho de desenho da interface do usuário executável

  1. Em stdafx.h, adicione as políticas de #include :

    #include <agents.h>
    #include <ppl.h>
    
  2. Em ChildView.h, adicione task_group e variáveis do membro de unbounded_buffer a seção de protected de CChildView classe. O objeto de task_group contém as tarefas que executam o desenho; o objeto de unbounded_buffer contém a imagem concluída de Mandelbrot.

    concurrency::task_group m_DrawingTasks;
    concurrency::unbounded_buffer<BitmapPtr> m_MandelbrotImages;
    
  3. Em ChildView.cpp, adicionar uma política de using ao namespace de concurrency .

    using namespace concurrency;
    
  4. No método de CChildView::DrawMandelbrot , depois da chamada a Bitmap::UnlockBits, chame a função de concurrency::send para passe o objeto de Bitmap ao thread de interface do usuário. Em postar uma mensagem de pintura para o thread de interface do usuário e invalide a área do cliente.

    // Unlock the bitmap from system memory.
    pBitmap->UnlockBits(&bitmapData);
    
    // Add the Bitmap object to image queue.
    send(m_MandelbrotImages, pBitmap);
    
    // Post a paint message to the UI thread.
    PostMessage(WM_PAINT);
    // Invalidate the client area.
    InvalidateRect(NULL, FALSE);
    
  5. Atualizar o método de CChildView::OnPaint para receber o objeto atualizado de Bitmap e para desenhar a imagem à janela do cliente.

    void CChildView::OnPaint() 
    {
       CPaintDC dc(this); // device context for painting 
    
       // If the unbounded_buffer object contains a Bitmap object,  
       // draw the image to the client area.
       BitmapPtr pBitmap;
       if (try_receive(m_MandelbrotImages, pBitmap))
       {
          if (pBitmap != NULL)
          {
             // Draw the bitmap to the client area.
             Graphics g(dc);
             g.DrawImage(pBitmap.get(), 0, 0);
          }
       }
       // Draw the image on a worker thread if the image is not available. 
       else
       {
          RECT rc;
          GetClientRect(&rc);
          m_DrawingTasks.run([rc,this]() {
             DrawMandelbrot(BitmapPtr(new Bitmap(rc.right, rc.bottom)));
          });
       }
    }
    

    O método de CChildView::OnPaint cria uma tarefa gerar a imagem de Mandelbrot se não existir uma no buffer de mensagem. O buffer de mensagem não conterá um objeto de Bitmap em casos como a mensagem inicial de pintura e a outra janela é movida na frente da janela do cliente.

  6. Verifique se o aplicativo seja atualizado com êxito criando e executando o.

Interface do usuário é agora mais respondente porque o trabalho de desenho é executado em segundo plano.

[Superior]

Aprimorando o desempenho de desenho

A geração de fractal de Mandelbrot é uma boa candidata para o parallelization porque a computação de cada x é independente de todas computações restantes. Para parallelize o procedimento de desenho, converta o loop externo de for no método de CChildView::DrawMandelbrot a uma chamada para o algoritmo de concurrency::parallel_for , como a seguir.

// Compute whether each point lies in the Mandelbrot set.
parallel_for (0u, height, [&](UINT row)
{
   // Loop body omitted for brevity.
});

Como a computação de cada elemento de bitmap é independente, não é necessário sincronizar as operações de desenho que acessam a memória de bitmap. Isso permite que o desempenho para dimensionar à medida que o número de processadores disponíveis aumenta.

[Superior]

Adicionando suporte para o cancelamento

Esta seção descreve como controlar a janela que é redimensionado e como cancelar todas as tarefas ativas de desenho quando a janela é destruída.

O documento Cancelamento no PPL explica como cancelar o trabalha em tempo de execução. O cancelamento é cooperativo; consequentemente, não acontecerá imediatamente. Para parar uma tarefa cancelada, o tempo de execução gerará uma exceção interna durante uma chamada subsequente da tarefa no tempo de execução. A seção anterior mostra como usar o algoritmo de parallel_for para melhorar o desempenho da tarefa de desenho. A chamada para parallel_for permite que o tempo de execução para interromper a tarefa consequentemente, e permite o cancelamento para trabalhar.

Cancelando tarefas ativas

O aplicativo de Mandelbrot cria os objetos de Bitmap cujas dimensões correspondem ao tamanho da janela do cliente. Cada vez que a janela do cliente é redimensionada, o aplicativo cria uma tarefa em segundo plano adicional gerar uma imagem para o tamanho da janela nova. O aplicativo não exigir essas imagens intermediários; requer apenas a imagem para o tamanho final da janela. Para impedir que o aplicativo execute esse trabalho adicional, poderá cancelar todas as tarefas ativas de desenho nos manipuladores de mensagem para mensagens de WM_SIZE e de WM_SIZING e depois reagendar o trabalho de desenho depois que a janela é redimensionada.

Para cancelar tarefas ativas de desenho quando a janela é redimensionada, o aplicativo chama o método de concurrency::task_group::cancel nos manipuladores para as mensagens de WM_SIZING e de WM_SIZE . O manipulador para a mensagem de WM_SIZE também chamará o método de concurrency::task_group::wait a esperar para concluir todas as tarefas ativas e reagendar na tarefa de desenho para o tamanho atualizado da janela.

Quando a janela do cliente é destruída, é uma prática recomendada para cancelar todas as tarefas ativas de desenho. Cancelar todas as tarefas ativas de desenho assegura que os threads de trabalho não postam mensagens ao thread de interface do usuário depois que a janela do cliente é destruída. O aplicativo cancela todas as tarefas ativas de desenho do manipulador da mensagem de WM_DESTROY .

Resposta ao cancelamento

O método de CChildView::DrawMandelbrot , que executa a tarefa de desenho, deve responder a ser cancelado. Como o tempo de execução usa a manipulação de exceção para cancelar tarefas, o método de CChildView::DrawMandelbrot deve usar exceções gerais um mecanismo seguro para garantir que todos os recursos são limpos corretamente. Este exemplo usa o padrão Aquisição de recurso é inicialização (RAII) para garantir que os bits de bitmap são desbloqueados quando a tarefa é cancelada.

Para adicionar suporte para o cancelamento do aplicativo de Mandelbrot

  1. Em ChildView.h, na seção de protected da classe de CChildView , adicione instruções para OnSize, OnSizing, e funções do mapa da mensagem de OnDestroy .

    afx_msg void OnPaint();
    afx_msg void OnSize(UINT, int, int);
    afx_msg void OnSizing(UINT, LPRECT); 
    afx_msg void OnDestroy();
    DECLARE_MESSAGE_MAP()
    
  2. Em ChildView.cpp, modifique o mapa de mensagem para manipuladores para conter WM_SIZE, WM_SIZING, e mensagens de WM_DESTROY .

    BEGIN_MESSAGE_MAP(CChildView, CWnd)
       ON_WM_PAINT()
       ON_WM_SIZE()
       ON_WM_SIZING()
       ON_WM_DESTROY()
    END_MESSAGE_MAP()
    
  3. Implemente o método CChildView::OnSizing. Esse método cancela todas as tarefas existentes de desenho.

    void CChildView::OnSizing(UINT nSide, LPRECT lpRect)
    {
       // The window size is changing; cancel any existing drawing tasks.
       m_DrawingTasks.cancel();
    }
    
  4. Implemente o método CChildView::OnSize. Esse método cancela todas as tarefas existentes de desenho e crie uma nova tarefa de desenho para o tamanho atualizado da janela do cliente.

    void CChildView::OnSize(UINT nType, int cx, int cy)
    {
       // The window size has changed; cancel any existing drawing tasks.
       m_DrawingTasks.cancel();
       // Wait for any existing tasks to finish.
       m_DrawingTasks.wait();
    
       // If the new size is non-zero, create a task to draw the Mandelbrot  
       // image on a separate thread. 
       if (cx != 0 && cy != 0)
       {      
          m_DrawingTasks.run([cx,cy,this]() {
             DrawMandelbrot(BitmapPtr(new Bitmap(cx, cy)));
          });
       }
    }
    
  5. Implemente o método CChildView::OnDestroy. Esse método cancela todas as tarefas existentes de desenho.

    void CChildView::OnDestroy()
    {
       // The window is being destroyed; cancel any existing drawing tasks.
       m_DrawingTasks.cancel();
       // Wait for any existing tasks to finish.
       m_DrawingTasks.wait();
    }
    
  6. Em ChildView.cpp, defina a classe de scope_guard , que implementa o padrão de RAII.

    // Implements the Resource Acquisition Is Initialization (RAII) pattern  
    // by calling the specified function after leaving scope. 
    class scope_guard 
    {
    public:
       explicit scope_guard(std::function<void()> f)
          : m_f(std::move(f)) { }
    
       // Dismisses the action. 
       void dismiss() {
          m_f = nullptr;
       }
    
       ~scope_guard() {
          // Call the function. 
          if (m_f) {
             try {
                m_f();
             }
             catch (...) {
                terminate();
             }
          }
       }
    
    private:
       // The function to call when leaving scope.
       std::function<void()> m_f;
    
       // Hide copy constructor and assignment operator.
       scope_guard(const scope_guard&);
       scope_guard& operator=(const scope_guard&);
    };
    
  7. Adicione o seguinte código para o método de CChildView::DrawMandelbrot depois da chamada a Bitmap::LockBits:

    // Create a scope_guard object that unlocks the bitmap bits when it 
    // leaves scope. This ensures that the bitmap is properly handled 
    // when the task is canceled.
    scope_guard guard([&pBitmap, &bitmapData] {
       // Unlock the bitmap from system memory.
       pBitmap->UnlockBits(&bitmapData);      
    });
    

    Esse código trata o cancelamento criando um objeto de scope_guard . Quando as folhas objeto do escopo, desbloqueia os bits de bitmap.

  8. Modifique o final do método de CChildView::DrawMandelbrot para ignorar o objeto de scope_guard depois que os bits de bitmap são desbloqueados, mas antes de todas as mensagens sejam enviadas ao thread de interface do usuário. Isso assegura que o thread de interface do usuário não seja atualizado antes que os bits de bitmap sejam desbloqueados.

    // Unlock the bitmap from system memory.
    pBitmap->UnlockBits(&bitmapData);
    
    // Dismiss the scope guard because the bitmap has been  
    // properly unlocked.
    guard.dismiss();
    
    // Add the Bitmap object to image queue.
    send(m_MandelbrotImages, pBitmap);
    
    // Post a paint message to the UI thread.
    PostMessage(WM_PAINT);
    // Invalidate the client area.
    InvalidateRect(NULL, FALSE);
    
  9. Verifique se o aplicativo seja atualizado com êxito criando e executando o.

Quando você redimensionar a janela, para desenhar o trabalho será executado apenas para o tamanho final da janela. Todas as tarefas ativas de desenho são canceladas também quando a janela é destruída.

[Superior]

Consulte também

Conceitos

Paralelismo de tarefa (tempo de execução de simultaneidade)

Blocos de mensagens assíncronos

Funções de transmissão de mensagem

Algoritmos paralelos

Cancelamento no PPL

Outros recursos

Instruções passo a passo do Tempo de Execução de Simultaneidade

Aplicativos para desktop do MFC