Wskazówki: usuwanie pracy z wątku interfejs użytkownika
Ten dokument demonstruje, jak przenieść pracy wykonanej przez wątek interfejsu użytkownika (UI) w aplikacji Microsoft Foundation Classes (MFC) do wątku roboczego za pomocą Runtime współbieżności.Dokument ten ilustruje też sposób zwiększyć wydajność czasochłonnych operacji rysowania.
Usuwanie pracy z wątku interfejsu użytkownika przez odciążenie blokującej operacji, na przykład rysunek wątków roboczych można poprawić sprawność aplikacji.Użyto rysunku procedura, która generuje podobieństwa Mandelbrot wykazać czasochłonna operacja blokująca.Generowanie podobieństwa Mandelbrot jest również odpowiednie do pełnienia funkcji wykonywania obliczeń każdego piksela jest niezależne od wszystkich pozostałych obliczeń.
Wymagania wstępne
Przed rozpoczęciem tego instruktażu, przeczytaj następujące tematy:
Zaleca się również aby zrozumieć podstawy projektowania aplikacji MFC i GDI+ przed rozpoczęciem tego instruktażu.Aby uzyskać więcej informacji na temat MFC, zobacz Aplikacje dla Pulpitu MFC.Aby uzyskać więcej informacji o GDI+, zobacz GDI +.
Sekcje
Ten instruktaż zawiera następujące sekcje:
Tworzenie aplikacji MFC
Zaimplementowanie szeregowego wersji aplikacji Mandelbrot
Usuwanie pracy z wątku interfejsu użytkownika
Zwiększanie wydajności rysunku
Dodawanie obsługi anulowania
Tworzenie aplikacji MFC
W tej sekcji opisano sposób tworzenia podstawowych aplikacji MFC.
Aby utworzyć aplikację Visual C++ MFC
Na pliku menu, kliknij Nowy, a następnie kliknij przycisk Projekt.
W Nowy projekt dialogowe, w Szablonów okienku wybierz Visual C++, a następnie, w szablonów okienku wybierz Aplikacja MFC.Wpisz nazwę projektu, na przykład Mandelbrot, a następnie kliknij przycisk OK do wyświetlania Kreatora aplikacji MFC.
W Typ aplikacji okienku wybierz jednolitego dokumentu.Zapewnić, że /Widok dokumentu architektura obsługuje pole wyboru jest wyczyszczone.
Kliknij Zakończ do tworzenia projektu i zamknąć Kreatora aplikacji MFC.
Sprawdź, czy aplikacja została pomyślnie utworzona, budowania i uruchamiając go.Do tworzenia aplikacji, z budować menu, kliknij Zbudować roztwór.Jeśli aplikacja tworzy się pomyślnie, należy uruchomić aplikację, klikając Start Debugging na debugowania menu.
Zaimplementowanie szeregowego wersji aplikacji Mandelbrot
W tej sekcji opisano sposób rysowania podobieństwa Mandelbrot.Ta wersja rysuje podobieństwa Mandelbrot do GDI+Bitmap obiekt, a następnie kopiuje zawartość tej mapy bitowej do okna klienta.
Aby zaimplementować szeregowego wersję aplikacji Mandelbrot
Dodaj następującą wartość w stdafx.h, #include dyrektywę:
#include <memory>
W ChildView.h po pragma dyrektywy, określenie BitmapPtr typu.BitmapPtr Typu umożliwia wskaźnik do Bitmap obiekt ma być udostępniane wielu składników.Bitmap Obiekt zostanie usunięty, gdy nie jest wywoływany przez dowolny składnik.
typedef std::shared_ptr<Gdiplus::Bitmap> BitmapPtr;
W ChildView.h, Dodaj następujący kod do protected sekcji CChildView klasy:
protected: // Draws the Mandelbrot fractal to the specified Bitmap object. void DrawMandelbrot(BitmapPtr); protected: ULONG_PTR m_gdiplusToken;
W ChildView.cpp lub usunąć następujące wiersze.
//#ifdef _DEBUG //#define new DEBUG_NEW //#endif
W kompilacje debugowania tego kroku uniemożliwia korzystanie z aplikacji DEBUG_NEW programu przydzielania, która jest niezgodna z GDI+.
Dodaj ChildView.cpp, using dyrektywa do Gdiplus obszaru nazw.
using namespace Gdiplus;
Dodaj następujący kod do konstruktora i destruktora z CChildView klasy do inicjowania i zamknięty GDI+.
CChildView::CChildView() { // Initialize GDI+. GdiplusStartupInput gdiplusStartupInput; GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); } CChildView::~CChildView() { // Shutdown GDI+. GdiplusShutdown(m_gdiplusToken); }
Wdrożenie CChildView::DrawMandelbrot metody.Ta metoda zwraca podobieństwa Mandelbrot na określony Bitmap obiektu.
// 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); }
Wdrożenie CChildView::OnPaint metody.Ta metoda wymaga CChildView::DrawMandelbrot , a następnie skopiuje zawartość Bitmap obiektu do okna.
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); } }
Zweryfikuj, że wniosek został pomyślnie zaktualizowany budowania i uruchamiając go.
Poniższa ilustracja pokazuje wyniki stosowania Mandelbrot.
Obliczeń dla każdego piksela jest drogie w praktyce, wątek interfejsu użytkownika nie może przetworzyć dodatkowe wiadomości, dopóki nie zakończy się przy obliczaniu ogólnej.Może to zmniejszyć czas reakcji w aplikacji.Jednakże można zwolnić ten problem, usuwając pracy z wątku interfejsu użytkownika.
Top
Usuwanie pracy z wątku interfejsu użytkownika
W tej sekcji przedstawiono sposób usuwania rysunku pracy z wątku interfejsu użytkownika w aplikacji Mandelbrot.Przenosząc na rysunku pracy z wątku interfejsu użytkownika do wątku roboczego wątku interfejsu użytkownika może przetwarzać wiadomości jako wątku roboczego generuje obraz w tle.
Runtime współbieżności udostępnia trzy sposoby uruchamiania zadań: grup zadań, asynchronicznego agenci, i zadań lekkie.Chociaż jeden z tych mechanizmów można użyć do usunięcia pracy z wątku interfejsu użytkownika, w tym przykładzie concurrency::task_group obiektu, ponieważ grup zadań obsługuje anulowania.Później użyto anulowania zmniejszyć ilość pracy wykonanej, gdy zmieniany jest rozmiar okna klienta i wykonać oczyszczanie, gdy okno jest niszczone.
W tym przykładzie użyto także concurrency::unbounded_buffer obiekt, aby umożliwić wątku interfejsu użytkownika i wątek roboczy, aby komunikować się ze sobą.Po wątek roboczy tworzy obraz, wysyła wskaźnik do Bitmap obiektu do unbounded_buffer obiekt, a następnie księguje wiadomość programu paint do wątku interfejsu użytkownika.Wątek interfejsu użytkownika otrzyma od unbounded_buffer obiektu Bitmap object i zwraca ją do okna klienta.
Aby usunąć rysunku pracy z wątku interfejsu użytkownika
Dodaj następującą wartość w stdafx.h, #include dyrektyw:
#include <agents.h> #include <ppl.h>
W ChildView.h, dodać task_group i unbounded_buffer , aby protected sekcji CChildView klasy.task_group Obiekt zawiera zadania, które wykonują rysunku; unbounded_buffer obiekt posiada zakończonych obrazu Mandelbrot.
concurrency::task_group m_DrawingTasks; concurrency::unbounded_buffer<BitmapPtr> m_MandelbrotImages;
Dodaj ChildView.cpp, using dyrektywa do concurrency obszaru nazw.
using namespace concurrency;
W CChildView::DrawMandelbrot po wywołaniu metody Bitmap::UnlockBits, call concurrency::send funkcji, aby przekazać Bitmap obiektu wątku interfejsu użytkownika.Następnie ogłosić wiadomość programu paint do wątku interfejsu użytkownika i unieważnia obszaru klienta.
// 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);
Aktualizacja CChildView::OnPaint metoda uzyskania zaktualizowanego Bitmap obiektów i narysuj obrazu w oknie klienta.
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))); }); } }
CChildView::OnPaint Metoda tworzy zadanie do generowania obrazu Mandelbrot, jeśli nie istnieje w buforze wiadomości.Bufor komunikatów nie będzie zawierać Bitmap obiektu w przypadkach, takich jak wiadomości początkowej paint i innego okna, jest przenoszony z przodu okna klienta.
Zweryfikuj, że wniosek został pomyślnie zaktualizowany budowania i uruchamiając go.
Interfejs użytkownika jest teraz sprawniejszego, ponieważ rysunku praca jest wykonywana w tle.
Top
Zwiększanie wydajności rysunku
Generowanie podobieństwa Mandelbrot jest dobrym kandydatem wykonywania obliczeń każdego piksela jest niezależne od wszystkich pozostałych obliczeń.Konwertuj zewnętrznego do zrównoleglenia procedurę rysunku, for w pętli w CChildView::DrawMandelbrot metody wywołania concurrency::parallel_for algorytm w następujący sposób.
// Compute whether each point lies in the Mandelbrot set.
parallel_for (0u, height, [&](UINT row)
{
// Loop body omitted for brevity.
});
Ponieważ przy obliczaniu każdego elementu mapy bitowej jest niezależna, ma synchronizować operacji rysowania, które pamięci mapy bitowej.Dzięki temu wydajność przeskalować jako liczba dostępnych procesorów wzrasta.
Top
Dodawanie obsługi anulowania
W tej sekcji opisano sposoby uchwyt zmiany rozmiaru okna oraz anulować wszystkie aktywne zadania rysunku, gdy okno jest niszczone.
Dokument Anulowanie w PPL wyjaśnia, jak działa anulowania w czasie wykonywania.Anulowanie jest w zakresie współpracy; w związku z tym gdy nie występuje natychmiast.Aby zatrzymać zadanie, anulowane, środowiska wykonawczego zgłasza wyjątek wewnętrzny podczas wywołania kolejne zadania w czasie wykonywania.Poprzedniej sekcji przedstawiono sposób użycia parallel_for algorytm, aby zwiększyć wydajność rysowania zadania.Wywołanie parallel_for umożliwia runtime zatrzymać zadanie, a zatem umożliwia anulowanie pracy.
Anulowanie zadania aktywne
Tworzy aplikację Mandelbrot Bitmap obiektów, których wymiary zgodny z rozmiarem okna klienta.Przy każdym rozmiaru okna klienta aplikacji tworzy zadanie dodatkowe tła do generowania obrazu dla nowego rozmiaru okna.Aplikacja nie wymagają tych obrazów pośrednich; wymaga on tylko obraz do rozmiaru okna końcowego.Aby zapobiec aplikacji z wykonywania dodatkowej pracy, można anulować wszystkie aktywne zadania rysunku w procedurach obsługi wiadomości dla WM_SIZE i WM_SIZING wiadomości, a następnie rysunku zmiany harmonogramu pracy po rozmiaru okna.
Aby anulować zadania aktywne rysunku, gdy zmieniany jest rozmiar okna, aplikacja wywołuje concurrency::task_group::cancel metoda obsługi dla WM_SIZING i WM_SIZE wiadomości.Programu obsługi WM_SIZE wiadomości również wywołania concurrency::task_group::wait metoda oczekiwania dla wszystkich aktywnych zadań do wykonania i następnie dokonało zadanie rysunku do rozmiaru okna zaktualizowane.
Zniszczenia okna klienta jest dobrą praktyką, aby anulować wszystkie aktywne zadania rysunku.Anulowanie wszystkich aktywnych zadań rysunku upewnia się, że wątki robocze nie ogłaszania wiadomości w wątku interfejsu użytkownika po okna klienta jest niszczony.Aplikacja anuluje wszystkie aktywne zadania rysunku programu obsługi dla WM_DESTROY wiadomości.
Odpowiada na anulowanie
CChildView::DrawMandelbrot Metodę, która wykonuje zadanie rysunku, musi odpowiadać na anulowanie.Ponieważ środowisko wykonawcze używa obsługi wyjątków do anulowania zadania, CChildView::DrawMandelbrot metody należy użyć mechanizmu bezpiecznego wyjątek do zagwarantowania, że wszystkie zasoby są poprawnie oczyszczony.W tym przykładzie Inicjowania jest przejęcie zasobu (RAII) deseniu do zagwarantowania, że bitów bitmapy są odblokowane po anulowaniu zadania.
Aby dodać obsługę anulowania w aplikacji Mandelbrot
W ChildView.h w protected sekcji CChildView klasy, dodać deklaracje OnSize, OnSizing, i OnDestroy komunikat funkcji mapę.
afx_msg void OnPaint(); afx_msg void OnSize(UINT, int, int); afx_msg void OnSizing(UINT, LPRECT); afx_msg void OnDestroy(); DECLARE_MESSAGE_MAP()
W ChildView.cpp, należy zmodyfikować mapę wiadomość zawiera obsługę dla WM_SIZE, WM_SIZING, i WM_DESTROY wiadomości.
BEGIN_MESSAGE_MAP(CChildView, CWnd) ON_WM_PAINT() ON_WM_SIZE() ON_WM_SIZING() ON_WM_DESTROY() END_MESSAGE_MAP()
Wdrożenie CChildView::OnSizing metody.Ta metoda anuluje żadnych istniejących zadań rysunku.
void CChildView::OnSizing(UINT nSide, LPRECT lpRect) { // The window size is changing; cancel any existing drawing tasks. m_DrawingTasks.cancel(); }
Wdrożenie CChildView::OnSize metody.Ta metoda anuluje żadnych istniejących zadań rysunku i tworzy nowe zadanie rysunku do rozmiaru okna klienta zaktualizowane.
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))); }); } }
Wdrożenie CChildView::OnDestroy metody.Ta metoda anuluje żadnych istniejących zadań rysunku.
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(); }
W ChildView.cpp, scope_guard klasy, która implementuje deseń 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&); };
Dodaj następujący kod do CChildView::DrawMandelbrot metody po wywołaniu 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); });
Kod ten obsługuje anulowanie tworząc scope_guard obiektu.Gdy obiekt pozostawia, odblokowuje bitów mapy bitowej.
Modyfikowanie końca CChildView::DrawMandelbrot metodę, aby odrzucić scope_guard obiektu po bitach bitmapy są odblokowane, ale przed wszystkie wiadomości są wysyłane do wątku interfejsu użytkownika.Dzięki temu wątku interfejsu użytkownika nie jest aktualizowany przed bitów bitmapy są odblokowane.
// 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);
Zweryfikuj, że wniosek został pomyślnie zaktualizowany budowania i uruchamiając go.
Podczas zmiany rozmiaru okna rysunku pracy wykonywana jest tylko rozmiar okna końcowego.Okno jest niszczone również anulowane są wszystkie aktywne zadania rysunku.
Top
Zobacz też
Koncepcje
Równoległość zadania (współbieżność środowiska wykonawczego)
Bloki komunikatów asynchronicznych
Funkcje przekazywania komunikatów