Compartilhar via


Demonstra Passo a passo: Removendo o trabalho de um segmento de Interface do usuário

Este documento demonstra como usar o Runtime de simultaneidade para mover o trabalho realizado pelo segmento de interface de usuário (UI) em um aplicativo do Microsoft Foundation Classes (MFC) para um segmento de trabalho. Este documento também demonstra como melhorar o desempenho de uma operação demorada de desenho.

Removendo o trabalho de thread da UI, transferindo a carga de bloqueio de operações, por exemplo, desenho, para threads de trabalho pode melhorar a capacidade de resposta do seu aplicativo. Esta explicação passo a passo usa uma rotina de desenho que gera o fractal Mandelbrot para demonstrar uma longa operação de bloqueio. A geração do fractal Mandelbrot também é um bom candidato para a paralelização porque a computação de cada pixel é independente de todos os outros cálculos.

Pré-requisitos

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

Também recomendamos que você entenda os fundamentos do desenvolvimento de aplicativos MFC e GDI+ antes de iniciar esta explicação passo a passo. Para obter mais informações sobre o MFC, consulte MFC Reference. Para obter mais informações sobre GDI+, consulte GDI+.

Seções

Esta explicação passo a passo contém as seções a seguir:

  • Criando o aplicativo MFC

  • Implementando a Serial versão do aplicativo Mandelbrot

  • Removendo o trabalho do segmento de Interface do usuário

  • Melhorando o desempenho de desenho

  • Adicionando suporte a cancelamento

Criando o aplicativo MFC

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

Para criar um aplicativo MFC do Visual C++

  1. No menu File, clique em New, e em seguida, clique em Project

  2. No Novo projeto na caixa de Installed Templates painel, selecione **Visual C++**e, em seguida, no modelos painel, selecione Aplicativo MFC. Digite um nome para o projeto, por exemplo, Mandelbrote em seguida, clique em OK para exibir o mfc Application Wizard.

  3. No O tipo de aplicativo painel, selecione único documento. Certifique-se de que o suporte a arquitetura de exibição do documento caixa de seleção está desmarcada.

  4. Clique em Concluir para criar o projeto e fechar o mfc Application Wizard.

    Verifique se o aplicativo foi criado com êxito, criando e executando-o. Para criar o aplicativo, na Build menu, clique em Build Solution. Se o aplicativo foi criado com êxito, execute o aplicativo, clicando em Start Debugging sobre o Debug menu.

Implementando a Serial versão do aplicativo Mandelbrot

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

Para implementar a versão serial do aplicativo Mandelbrot

  1. Em stdafx. h, adicione o seguinte #include diretiva:

    #include <memory>
    
  2. Em ChildView.h, após a pragma diretiva, definir o BitmapPtr tipo. O BitmapPtr tipo permite que um ponteiro para uma Bitmap o objeto a ser compartilhada por vários componentes. O Bitmap objeto é excluído quando ele não é mais consultado pelo componente.

    typedef std::shared_ptr<Gdiplus::Bitmap> BitmapPtr;
    
  3. No ChildView.h, adicione o seguinte código para o protected seção a CChildView classe:

    protected:
       // Draws the Mandelbrot fractal to the specified Bitmap object.
       void DrawMandelbrot(BitmapPtr);
    
    protected:
       ULONG_PTR m_gdiplusToken;
    
  4. Na ChildView.cpp, comente ou remova as seguintes linhas.

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

    Em compilações de depuração, esta etapa impede que o aplicativo usando o DEBUG_NEW alocador, que é incompatível com GDI+.

  5. No ChildView.cpp, adicione um using diretiva para o Gdiplus namespace.

    using namespace Gdiplus;
    
  6. Adicione o seguinte código ao construtor e destruidor da CChildView classe para inicializar e desligar 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 para o Bitmap objeto.

    // 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. Este método chama CChildView::DrawMandelbrot e, em seguida, copia o conteúdo da Bitmap o objeto para a 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 foi atualizado com êxito construindo e executá-lo.

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

O aplicativo de Mandelbrot

Como a computação para cada pixel é dispendiosa, o segmento de interface do usuário não pode processar mensagens adicionais, até que termine de computação geral. Isso pode diminuir a capacidade de resposta do aplicativo. No entanto, você pode aliviar esse problema removendo o trabalho do thread da interface do usuário.

go to top

Removendo o trabalho do segmento de UI

Esta seção mostra como remover o trabalho de desenho do thread da interface do usuário no aplicativo Mandelbrot. Movendo desenho de trabalho do segmento de UI para um thread de trabalho, o segmento de interface do usuário pode processar mensagens, como o segmento de trabalho gera a imagem no plano de fundo.

O Runtime de simultaneidade fornece três maneiras de executar tarefas: grupos de tarefas, agentes assíncronos, e tarefas leves. Embora você possa usar qualquer um desses mecanismos para remover o trabalho de thread da UI, este exemplo usa um Concurrency::task_group porque a grupos de tarefas oferece suporte ao cancelamento. Esta explicação passo a passo posterior usa o cancelamento para reduzir a quantidade de trabalho é executada quando a janela do cliente é redimensionada e para executar a limpeza quando a janela é destruída.

Este exemplo também usa um Concurrency::unbounded_buffer o objeto para permitir que o thread de interface do usuário e o segmento de trabalho para se comunicar entre si. Depois que o thread de trabalho produz uma imagem, ele envia um ponteiro para o Bitmap o objeto para o unbounded_buffer de objeto e, em seguida, posta uma mensagem de pintura para o segmento de interface do usuário. O segmento de interface do usuário recebe depois do unbounded_buffer objeto do Bitmap object e desenha-lo para a janela do cliente.

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

  1. Em stdafx. h, adicione o seguinte #include diretivas:

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

    Concurrency::task_group m_DrawingTasks;
    Concurrency::unbounded_buffer<BitmapPtr> m_MandelbrotImages;
    
  3. No ChildView.cpp, adicione um using diretiva para o Concurrency namespace.

    using namespace Concurrency;
    
  4. No CChildView::DrawMandelbrot método, após a chamada para Bitmap::UnlockBits, chamar o Concurrency::send função para passar o Bitmap o objeto para o segmento de interface do usuário. Em seguida, postar uma mensagem de pintura para o segmento de interface do usuário e invalidar 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. Atualização do CChildView::OnPaint método para receber o documento atualizado Bitmap object e desenhe a imagem para a 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 CChildView::OnPaint método cria uma tarefa para gerar a imagem de Mandelbrot se não existir na mensagem de buffer. O buffer de mensagem não conterá um Bitmap o objeto em casos como, por exemplo, a mensagem inicial de pintura e quando outra janela é movida na frente da janela do cliente.

  6. Verifique se o aplicativo foi atualizado com êxito construindo e executá-lo.

A interface do usuário agora está mais responsivo, porque o trabalho de desenho é executado em segundo plano.

go to top

Melhorando o desempenho de desenho

A geração do fractal Mandelbrot é um bom candidato para a paralelização porque a computação de cada pixel é independente de todos os outros cálculos. Em paralelo o procedimento de desenho, converter o externo for loop na CChildView::DrawMandelbrot método para uma chamada para o Concurrency::parallel_for algoritmo, da seguinte maneira.

// 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 o acessam à memória do bitmap. Isso permite que o desempenho de expandir conforme o aumento do número de processadores disponíveis.

go to top

Adicionando suporte a cancelamento

Esta seção descreve como manipular o redimensionamento de janela e cancelar todas as tarefas de desenho ativos quando a janela é destruída.

O documento Cancelamento na PPL explica como funciona o cancelamento no tempo de execução. Cancelamento é cooperativo; Portanto, não ocorre imediatamente. Para interromper uma tarefa cancelada, o runtime lança uma exceção interna durante uma chamada subseqüente da tarefa no tempo de execução. A seção anterior mostra como usar o parallel_for o algoritmo para melhorar o desempenho da tarefa de desenho. A chamada para parallel_for permite que o tempo de execução interromper a tarefa e, portanto, permite o cancelamento para o trabalho.

Cancelando tarefas ativas

O aplicativo Mandelbrot cria Bitmap objetos cujas dimensões corresponder ao tamanho da janela do cliente. Sempre que a janela do cliente é redimensionada, o aplicativo cria uma tarefa de plano de fundo adicionais para gerar uma imagem para o novo tamanho de janela. O aplicativo não exigir essas imagens intermediárias; Ele requer apenas a imagem para o tamanho de janela final. Para impedir que o aplicativo executar esse trabalho adicional, você pode cancelar qualquer tarefa de desenho ativa nos manipuladores de mensagem para o WM_SIZE e WM_SIZING mensagens e, em seguida, desenho de reagendar trabalham depois que a janela é redimensionada.

Para cancelar o desenho de tarefas ativas quando a janela é redimensionada, o aplicativo chama o Concurrency::task_group::cancel método nos manipuladores para o WM_SIZING e WM_SIZE mensagens. O manipulador para o WM_SIZE mensagem também chamadas de Concurrency::task_group::wait método espera para o ativo de todas as tarefas para concluir e, em seguida, reagenda a tarefa de desenho para o tamanho da janela atualizado.

Quando a janela do cliente é destruída, é recomendável para cancelar quaisquer tarefas de desenho ativos. O cancelamento de quaisquer tarefas de desenho ativos certifica-se de que o threads de trabalho não postar mensagens para o segmento de interface do usuário após a janela do cliente é destruída. O aplicativo cancela quaisquer tarefas de desenho ativos no manipulador para o WM_DESTROY mensagem.

Responder ao cancelamento

O CChildView::DrawMandelbrot método, que realiza a tarefa de desenho, deve responder ao cancelamento. Porque o tempo de execução usa o tratamento de exceção para cancelar as tarefas, o CChildView::DrawMandelbrot método deve usar um mecanismo seguro de exceção para garantir que todos os recursos são o ativo. corretamente limpos-\ Este exemplo usa o É inicialização de aquisição de recursos padrão (RAII) para garantir que os bits de bitmap são desbloqueados quando a tarefa é cancelada.

Para adicionar o suporte para o cancelamento do aplicativo de Mandelbrot

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

    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. No ChildView.cpp, modifique o mapa da mensagem contenha manipuladores para o WM_SIZE, WM_SIZING, e WM_DESTROY mensagens.

    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. Este método cancela quaisquer tarefas de desenho existentes.

    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. Este método cancela quaisquer tarefas de desenho existentes e cria uma nova tarefa de desenho do tamanho da janela do cliente atualizado.

    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. Este método cancela quaisquer tarefas de desenho existentes.

    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. Na ChildView.cpp, defina a scope_guard classe, que implementa o padrão 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 CChildView::DrawMandelbrot método após a chamada para 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 manipula o cancelamento, criando uma scope_guard objeto. Quando o objeto sai do escopo, ele desbloqueia os bits de bitmap.

  8. Modificar o final o CChildView::DrawMandelbrot método para descartar a scope_guard objeto após os bits de bitmap são desbloqueados, mas antes de todas as mensagens são enviadas para o segmento de interface do usuário. Isso garante que o segmento de interface do usuário não é atualizado antes que os bits de bitmap são 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 foi atualizado com êxito construindo e executá-lo.

Quando você redimensiona a janela, trabalho de desenho é realizada somente para o tamanho da janela final. Quaisquer tarefas ativas do desenho também serão canceladas quando a janela é destruída.

go to top

Consulte também

Conceitos

Orientações de Runtime de simultaneidade

Paralelismo de tarefas (Runtime de simultaneidade)

Blocos de mensagens assíncronas

Funções de transmissão de mensagens

Algoritmos paralelos

Cancelamento na PPL

Outros recursos

MFC Reference