Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Det här dokumentet visar hur du använder Concurrency Runtime för att flytta det arbete som utförs av användargränssnittstråden (UI) i ett MFC-program (Microsoft Foundation Classes) till en arbetstråd. Det här dokumentet visar också hur du kan förbättra prestandan för en lång ritningsåtgärd.
Om du tar bort arbete från användargränssnittstråden genom att avlasta blockeringsåtgärder, till exempel ritning till arbetstrådar, kan du förbättra programmets svarstider. Den här genomgången använder en ritningsrutin som genererar Mandelbrot-fraktalen för att demonstrera en lång blockeringsåtgärd. Genereringen av mandelbrot fractal är också en bra kandidat för parallellisering eftersom beräkningen av varje pixel är oberoende av alla andra beräkningar.
Förutsättningar
Läs följande avsnitt innan du påbörjar den här genomgången:
Vi rekommenderar också att du förstår grunderna i MFC-programutveckling och GDI+ innan du påbörjar den här genomgången. Mer information om MFC finns i MFC Desktop-program. Mer information om GDI+ finns i GDI+.
Sektioner
Den här genomgången innehåller följande avsnitt:
Skapa MFC-programmet
I det här avsnittet beskrivs hur du skapar det grundläggande MFC-programmet.
Så här skapar du ett Visual C++ MFC-program
- Använd MFC-programguiden för att skapa ett MFC-program med alla standardinställningar. Mer information om hur du öppnar guiden för din version av Visual Studio finns i Genomgång: Använda de nya MFC Shell-kontrollerna . 
- Skriv ett namn för projektet, till exempel - Mandelbrot, och klicka sedan på OK för att visa MFC-programguiden.
- I fönstret Programtyp väljer du Enskilt dokument. Kontrollera att kryssrutan Stöd för dokument-/vyarkitektur är avmarkerad. 
- Klicka på Slutför för att skapa projektet och stäng MFC-programguiden. - Kontrollera att programmet har skapats framgångsrikt genom att bygga och köra det. Om du vill skapa programmet går du till menyn Skapa och klickar på Skapa lösning. Om programmet har skapats framgångsrikt, kör du programmet genom att klicka på Starta felsökning på felsökningsmenyn. 
Implementera serieversionen av Mandelbrot-programmet
I det här avsnittet beskrivs hur du ritar mandelbrot-fraktalen. Den här versionen ritar Mandelbrot-fraktalen till ett GDI+ Bitmap-objekt och kopierar sedan innehållet i bitmappen till klientfönstret.
Implementera serieversionen av Mandelbrot-programmet
- Lägg till följande direktiv i pch.h ( - #includei Visual Studio 2017 och tidigare):- #include <memory>
- Definiera - pragma-typen efter- BitmapPtr-direktivet i ChildView.h. Typen- BitmapPtrgör att en pekare till ett- Bitmapobjekt kan delas av flera komponenter. Objektet- Bitmaptas bort när det inte längre refereras till av någon komponent.- typedef std::shared_ptr<Gdiplus::Bitmap> BitmapPtr;
- I ChildView.h lägger du till följande kod i - protectedavsnittet i- CChildViewklassen:- protected: // Draws the Mandelbrot fractal to the specified Bitmap object. void DrawMandelbrot(BitmapPtr); protected: ULONG_PTR m_gdiplusToken;
- Kommentera ut eller ta bort följande rader i ChildView.cpp. - //#ifdef _DEBUG //#define new DEBUG_NEW //#endif- I Felsökningsversioner förhindrar det här steget att programmet använder - DEBUG_NEWallokeraren, som är inkompatibel med GDI+.
- I ChildView.cpp lägger du till ett - usingdirektiv i- Gdiplusnamnområdet.- using namespace Gdiplus;
- Lägg till följande kod i konstruktorn och destruktören för - CChildViewklassen för att initiera och stänga av GDI+.- CChildView::CChildView() { // Initialize GDI+. GdiplusStartupInput gdiplusStartupInput; GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); } CChildView::~CChildView() { // Shutdown GDI+. GdiplusShutdown(m_gdiplusToken); }
- Implementera metoden - CChildView::DrawMandelbrot. Den här metoden ritar mandelbrot-fraktalen till det angivna- Bitmapobjektet.- // 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); }
- Implementera metoden - CChildView::OnPaint. Den här metoden anropar- CChildView::DrawMandelbrotoch kopierar sedan innehållet i- Bitmapobjektet till fönstret.- 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); } }
- Kontrollera att programmet har uppdaterats genom att skapa och köra det. 
Följande bild visar resultatet av Mandelbrot-programmet.
               
              
            
Eftersom beräkningen för varje pixel är beräkningsmässigt dyr kan användargränssnittstråden inte bearbeta ytterligare meddelanden förrän den övergripande beräkningen har slutförts. Detta kan minska svarstiden i programmet. Du kan dock lösa det här problemet genom att ta bort arbete från användargränssnittstråden.
[Topp]
Ta bort arbete från användargränssnittstråden
Det här avsnittet visar hur du tar bort ritningsarbetet från användargränssnittstråden i Mandelbrot-programmet. Genom att flytta ritningsarbete från användargränssnittstråden till en arbetstråd kan användargränssnittstråden bearbeta meddelanden när arbetstråden genererar bilden i bakgrunden.
Concurrency Runtime innehåller tre sätt att köra uppgifter: aktivitetsgrupper, asynkrona agenter och enkla uppgifter. Även om du kan använda någon av dessa mekanismer för att ta bort arbete från användargränssnittstråden använder det här exemplet ett samtidighetsobjekt::task_group eftersom aktivitetsgrupper stöder annullering. Den här genomgången använder senare annullering för att minska mängden arbete som utförs när klientfönstret ändras och för att utföra rensning när fönstret förstörs.
Det här exemplet använder också ett samtidighet::unbounded_buffer-objekt för att aktivera användargränssnittstråden och arbetstråden för att kommunicera med varandra. När arbetstråden har skapat bilden skickar den en pekare till Bitmap objektet till unbounded_buffer objektet och postar sedan ett rederingsmeddelande till UI-tråden. Användargränssnittstråden tar sedan emot unbounded_buffer objektet från Bitmap objektet och ritar det i klientfönstret.
Ta bort ritningsarbetet från användargränssnittstråden
- I pch.h (stdafx.h i Visual Studio 2017 och tidigare) lägger du till följande - #includedirektiv:- #include <agents.h> #include <ppl.h>
- I ChildView.h lägger du till - task_groupoch- unbounded_buffermedlemsvariabler i- protectedavsnittet i- CChildViewklassen. Objektet- task_groupinnehåller de uppgifter som utför ritningen.- unbounded_bufferObjektet innehåller den slutförda Mandelbrot-bilden.- concurrency::task_group m_DrawingTasks; concurrency::unbounded_buffer<BitmapPtr> m_MandelbrotImages;
- I ChildView.cpp lägger du till ett - usingdirektiv i- concurrencynamnområdet.- using namespace concurrency;
- CChildView::DrawMandelbrotI -metoden anropar du funktionen- Bitmap::UnlockBitsefter anropet till för att skicka- Bitmapobjektet till användargränssnittstråden. Publicera sedan ett färgmeddelande i användargränssnittstråden och ogiltigförklara klientområdet.- // 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);
- CChildView::OnPaintUppdatera metoden för att ta emot det uppdaterade- Bitmapobjektet och dra avbildningen till klientfönstret.- 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))); }); } }- Metoden - CChildView::OnPaintskapar en uppgift för att generera Mandelbrot-avbildningen om det inte finns någon i meddelandebufferten. Meddelandebufferten innehåller inte ett- Bitmapobjekt i fall som det första färgmeddelandet och när ett annat fönster flyttas framför klientfönstret.
- Kontrollera att programmet har uppdaterats genom att skapa och köra det. 
Användargränssnittet är nu mer dynamiskt eftersom ritningsarbetet utförs i bakgrunden.
[Topp]
Förbättra ritningsprestanda
Genereringen av mandelbrot fractal är en bra kandidat för parallellisering eftersom beräkningen av varje pixel är oberoende av alla andra beräkningar. Om du vill parallellisera ritningsproceduren konverterar du den yttre for-loopen i CChildView::DrawMandelbrot-metoden till ett anrop till algoritmen concurrency::parallel_for, enligt följande.
// Compute whether each point lies in the Mandelbrot set.
parallel_for (0u, height, [&](UINT row)
{
   // Loop body omitted for brevity.
});
Eftersom beräkningen av varje bitmappselement är oberoende behöver du inte synkronisera de ritningsåtgärder som har åtkomst till bitmappsminnet. Detta gör att prestanda kan skalas när antalet tillgängliga processorer ökar.
[Topp]
Lägga till stöd för annullering
I det här avsnittet beskrivs hur du hanterar storleksändring av fönster och hur du avbryter aktiva ritningsuppgifter när fönstret förstörs.
Dokumentet Annullering i PPL förklarar hur annulleringen fungerar under körningen. Annulleringen är samarbetsinriktad; därför sker det inte omedelbart. För att stoppa en avbruten arbetsuppgift utlöser runtime-miljön ett internt undantag under ett efterföljande anrop från arbetsuppgiften till runtime-miljön. I föregående avsnitt visas hur du använder algoritmen parallel_for för att förbättra ritningsuppgiftens prestanda. Anropet till parallel_for gör det möjligt för körningen att stoppa uppgiften och gör det därför möjligt att avbryta arbetet.
Avbryta aktiva aktiviteter
Mandelbrot-programmet skapar Bitmap objekt vars dimensioner matchar storleken på klientfönstret. Varje gång klientfönstret ändras skapar programmet ytterligare en bakgrundsaktivitet för att generera en avbildning för den nya fönsterstorleken. Programmet kräver inte dessa mellanliggande avbildningar. det kräver bara avbildningen för den slutliga fönsterstorleken. Om du vill förhindra att programmet utför det här extra arbetet kan du avbryta alla aktiva ritningsuppgifter i meddelandehanterare för WM_SIZE- och WM_SIZING-meddelanden och schemalägga om ritningen när fönstret har ändrats storlek.
För att avbryta aktiva ritningsuppgifter när fönstret ändras, anropar programmet concurrency::task_group::cancel-metoden i hanterarna för meddelandena  och WM_SIZING. Hanteraren för WM_SIZE-meddelandet anropar också concurrency::task_group::wait-metoden för att vänta tills alla aktiva uppgifter har slutförts, och schemalägger sedan om ritningsaktiviteten för den uppdaterade fönsterstorleken.
När klientfönstret förstörs är det bra att avbryta alla aktiva ritningsuppgifter. Om du avbryter aktiva ritningsuppgifter ser du till att arbetstrådarna inte skickar meddelanden till användargränssnittstråden när klientfönstret har förstörts. Programmet avbryter alla pågående ritningsarbetsuppgifter i hanteraren för meddelandet WM_DESTROY.
Svara på annullering
Metoden CChildView::DrawMandelbrot , som utför ritningsuppgiften, måste svara på annulleringen. Eftersom körtiden använder undantagshantering för att avbryta uppgifter CChildView::DrawMandelbrot måste metoden använda en mekanism som är säker för undantag för att garantera att alla resurser frigörs korrekt. I det här exemplet används mönstret Resource Acquisition Is Initialization (RAII) för att garantera att bitmappsbitarna låss upp när uppgiften avbryts.
Lägga till stöd för annullering i Mandelbrot-programmet
- I ChildView.h, i - protected-avsnittet av- CChildView-klassen, lägg till deklarationer för meddelandekartfunktionerna- OnSize,- OnSizingoch- 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()
- I ChildView.cpp ändrar du meddelandekartan så att den innehåller hanterare för meddelandena - WM_SIZE,- WM_SIZINGoch .- WM_DESTROY- BEGIN_MESSAGE_MAP(CChildView, CWnd) ON_WM_PAINT() ON_WM_SIZE() ON_WM_SIZING() ON_WM_DESTROY() END_MESSAGE_MAP()
- Implementera metoden - CChildView::OnSizing. Den här metoden avbryter alla befintliga ritningsuppgifter.- void CChildView::OnSizing(UINT nSide, LPRECT lpRect) { // The window size is changing; cancel any existing drawing tasks. m_DrawingTasks.cancel(); }
- Implementera metoden - CChildView::OnSize. Den här metoden avbryter alla befintliga ritningsuppgifter och skapar en ny ritningsaktivitet för den uppdaterade klientfönstrets storlek.- 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))); }); } }
- Implementera metoden - CChildView::OnDestroy. Den här metoden avbryter alla befintliga ritningsuppgifter.- 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(); }
- I ChildView.cpp definierar du - scope_guardklassen, som implementerar RAII-mönstret.- // 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&); };
- Lägg till följande kod i - CChildView::DrawMandelbrotmetoden efter anropet till- 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); });- Den här koden hanterar annullering genom att skapa ett - scope_guardobjekt. När objektet lämnar omfånget, låser det upp bitmapbitarna.
- Ändra slutet av - CChildView::DrawMandelbrotmetoden för att stänga- scope_guardobjektet när bitmappsbitarna har låsts upp, men innan några meddelanden skickas till användargränssnittstråden. Detta säkerställer att UI-tråden inte uppdateras innan bitmappsbitarna låses upp.- // 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);
- Kontrollera att programmet har uppdaterats genom att skapa och köra det. 
När du ändrar storlek på fönstret utförs ritningsarbete endast för den slutliga fönsterstorleken. Alla aktiva ritningsuppgifter avbryts också när fönstret förstörs.
[Topp]
Se även
              Genomgång av samtidighetskörning
              Uppgiftsparallellitet
              Asynkrona meddelandeblock
              Funktioner för meddelandeöverföring
              Parallella algoritmer
              Annullering i PPL
              MFC Desktop-program