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++
No menu File, clique em New, e em seguida, clique em Project
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.
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.
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
Em stdafx. h, adicione o seguinte #include diretiva:
#include <memory>
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;
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;
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+.
No ChildView.cpp, adicione um using diretiva para o Gdiplus namespace.
using namespace Gdiplus;
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); }
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); }
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); } }
Verifique se o aplicativo foi atualizado com êxito construindo e executá-lo.
A ilustração a seguir mostra os resultados do aplicativo 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
Em stdafx. h, adicione o seguinte #include diretivas:
#include <agents.h> #include <ppl.h>
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;
No ChildView.cpp, adicione um using diretiva para o Concurrency namespace.
using namespace Concurrency;
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);
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.
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
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()
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()
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(); }
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))); }); } }
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(); }
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&); };
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.
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);
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