Sdílet prostřednictvím


Návod: Odstranění práce z vlákna uživatelského rozhraní

Tento dokument demonstruje použití Runtime souběžnosti přesunout práce, které je prováděno pomocí uživatelského rozhraní (UI) podprocesu aplikace Microsoft Foundation tříd MFC () pracovní podproces.Tento dokument také ukazuje, jak zvýšit výkon zdlouhavá operace kreslení.

Převedením blokující operace odebrání práce z podproces uživatelského rozhraní, například výkres na pracovních podprocesů může zlepšit reakce aplikace.Tento návod používá výkresu rutiny, která vygeneruje fraktálový Mandelbrot prokázat dlouhé blokující operace.Generování fraktálový Mandelbrot je také vhodná pro parallelization, protože výpočet každého pixelu je nezávislé na jiné výpočty.

Požadavky

Před zahájením tohoto postupu naleznete v následujících tématech:

Doporučujeme také můžete pochopit základy vývoje aplikací MFC a GDI+ před zahájením tohoto postupu.Další informace o MFC naleznete Běžné aplikace knihovny MFC.Další informace o GDI+, viz GDI +.

Oddíly

Tento návod obsahuje následující oddíly:

  • Vytvoření aplikace MFC

  • Provádění sériové verze aplikace Mandelbrot

  • Odebrání pracovního podprocesu uživatelského rozhraní

  • Zlepšení výkonu výkresu

  • Přidání podpory pro zrušení

Vytvoření aplikace MFC

Tato část popisuje, jak vytvořit základní aplikace MFC.

Vytvoření aplikace Visual C++, MFC

  1. V nabídce Soubor klikněte na příkaz Nový a potom klikněte na příkaz Projekt.

  2. V Nový projekt v dialogovém okně pole Nainstalované šablony podokně vyberte **Visual C++**a poté v šablony podokně vyberte Aplikace MFC.Zadejte název projektu, například Mandelbrota klepněte na tlačítko OK zobrazení Průvodce aplikace MFC.

  3. V Typ aplikace podokně vyberte jeden dokument.Zajistit, aby Architektura/zobrazení dokumentu podporu zaškrtnutí políčka.

  4. Klepněte na tlačítko Dokončit vytvořte projekt a Zavřít Průvodce aplikace MFC.

    Ověřte, že aplikace byla úspěšně vytvořena vytvořením a spuštěním.Na vytvoření aplikace, sestavení nabídky, klepněte na tlačítko Sestavit řešení.Pokud aplikace úspěšně navazuje, spusťte aplikaci klepnutím na Spustit ladění na ladění nabídce.

Provádění sériové verze aplikace Mandelbrot

Tato část popisuje způsob kreslení Mandelbrot fraktálový.Tato verze nakreslí fraktálový Mandelbrot na GDI+rastr objektu a pak zkopíruje obsah bitmapu do okna klienta.

K provedení sériové verze aplikace Mandelbrot

  1. V stdafx.h, přidejte následující #include směrnice:

    #include <memory>
    
  2. V ChildView.h po pragma směrnice definovat BitmapPtr typu.BitmapPtr Typ umožňuje ukazatel na Bitmap objektu má být sdílen více součástmi.Bitmap Objekt je odstraněn, pokud již odkazuje jakékoli součásti.

    typedef std::shared_ptr<Gdiplus::Bitmap> BitmapPtr;
    
  3. V ChildView.h, přidejte následující kód protected část CChildView třídy:

    protected:
       // Draws the Mandelbrot fractal to the specified Bitmap object.
       void DrawMandelbrot(BitmapPtr);
    
    protected:
       ULONG_PTR m_gdiplusToken;
    
  4. V ChildView.cpp poznámky, nebo odeberte následující řádky.

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

    V sestavení ladění tohoto kroku zabraňuje aplikaci používat DEBUG_NEW přidělování, který není kompatibilní s GDI+.

  5. V ChildView.cpp, přidejte using do směrnice Gdiplus oboru názvů.

    using namespace Gdiplus;
    
  6. Konstruktor a z destruktoru přidejte následující kód CChildView třídy pro inicializaci a vypnutí GDI+.

    CChildView::CChildView()
    {
       // Initialize GDI+.
       GdiplusStartupInput gdiplusStartupInput;
       GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
    }
    
    CChildView::~CChildView()
    {
       // Shutdown GDI+.
       GdiplusShutdown(m_gdiplusToken);
    }
    
  7. Implementujte metodu CChildView::DrawMandelbrot. Tato metoda nakreslí na zadaný fraktálový Mandelbrot Bitmap objektu.

    // 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. Implementujte metodu CChildView::OnPaint. Tato metoda volá CChildView::DrawMandelbrot a pak zkopíruje obsah Bitmap objektu 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);
       }
    }
    
  9. Ověřte, zda byl úspěšně aktualizován aplikace vytvořením a spuštěním.

Následující obrázek ukazuje výsledky aplikace Mandelbrot.

Mandelbrot aplikace

Výpočet pro každý obrazový bod je výpočetně náročné, uživatelské rozhraní nemůže zpracovat další zprávy, dokud neskončí výpočtu celkové.To by mohla snížit rychlost reakce v aplikaci.Tento problém však může osvobodit odebráním pracovní podproces uživatelského rozhraní.

Top

Odebrání pracovního podprocesu uživatelského rozhraní

Tato část ukazuje, jak odebrat uživatelské rozhraní v aplikaci Mandelbrot výkresu práce.Přesunutím výkresu práce z uživatelské rozhraní do pracovního podprocesu podproces uživatelského rozhraní zpracovává zprávy jako pracovní podproces generuje obrázek na pozadí.

Runtime souběžnosti nabízí tři způsoby úlohy: skupin úkolů, agenti asynchronní, a lehký úkoly.I když používáte některý z těchto mechanismů práce odebrat z podproces uživatelského rozhraní v tomto příkladu concurrency::task_group objektu, protože zrušení podporují skupiny úloh.Tento návod používá novější zrušení, snížit množství práce, která se provádí při změně velikosti okna klienta a při zničení okna provést vyčištění.

Tento příklad používá také concurrency::unbounded_buffer objektu povolit uživatelské rozhraní a pracovní podproces pro vzájemnou komunikaci.Po pracovní podproces vytvoří obraz, odešle ukazatel na Bitmap k objektu unbounded_buffer objektu a potom odešle zprávu malby uživatelské rozhraní.Uživatelské rozhraní je pak obdrží od unbounded_buffer objektu Bitmap objektu a nakreslí do okna klienta.

Odebrání výkresu práce z uživatelské rozhraní

  1. V stdafx.h, přidejte následující #include směrnic:

    #include <agents.h>
    #include <ppl.h>
    
  2. Přidat v ChildView.h, task_group a unbounded_buffer členské proměnné protected část CChildView třídy.task_group Objekt obsahuje úkoly, které provádějí výkresu; unbounded_buffer objekt obsahuje dokončené Mandelbrot obrazu.

    concurrency::task_group m_DrawingTasks;
    concurrency::unbounded_buffer<BitmapPtr> m_MandelbrotImages;
    
  3. V ChildView.cpp, přidejte using do směrnice concurrency oboru názvů.

    using namespace concurrency;
    
  4. V CChildView::DrawMandelbrot po volání metody Bitmap::UnlockBits, zavolejte concurrency::send funkci předat Bitmap objektu uživatelské rozhraní.Potom odeslat zprávu do uživatelské rozhraní programu Malování a neruší oblasti 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);
    
  5. Aktualizace CChildView::OnPaint metoda aktualizovaný Bitmap objektu a kreslení obrazu do okna 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 vytvoří úkol k vytvoření bitové kopie Mandelbrot, pokud neexistuje ve vyrovnávací paměti pro zprávy.Vyrovnávací paměť pro zprávy nebude obsahovat Bitmap objektu v případech, například Malování počáteční zprávy a jiné okno je přesunuta z okna klienta.

  6. Ověřte, zda byl úspěšně aktualizován aplikace vytvořením a spuštěním.

Uživatelské rozhraní je nyní rychleji, protože výkresu práce probíhá na pozadí.

Top

Zlepšení výkonu výkresu

Generování fraktálový Mandelbrot je vhodná pro parallelization, protože výpočet každého pixelu je nezávislé na jiné výpočty.Parallelize výkresu postupem převést vnější for smyčka v CChildView::DrawMandelbrot volání metody concurrency::parallel_for algoritmus způsobem.

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

Výpočtu jednotlivých bitmapových prvků je nezávislý, není nutné synchronizovat výkresu operací, které přístup k paměti bitmapy.Měřítko jako počet dostupných procesorů zvýšení výkonu díky.

Top

Přidání podpory pro zrušení

Tato část popisuje, jak zpracovat změny velikosti okna a zrušit všechny aktivní úkoly výkresu při okna zničen.

Dokument Zrušení v knihovně PPL vysvětluje, jak funguje zrušení v modulu runtime.Zrušení je spolupráce; proto neprobíhá okamžitě.Zrušené úloh zastavíte runtime výjimku vnitřní během následné volání z úkolu do modulu runtime.V předchozí části zobrazuje způsob použití parallel_for algoritmus pro zlepšení výkonu úkolu výkresu.Volání parallel_for umožňuje runtime zastavit úlohu a proto umožňuje zrušení pracovat.

Zrušení aktivní úkoly

Vytvoří aplikaci Mandelbrot Bitmap objekty, jejichž rozměry odpovídají velikosti okna klienta.Při každé změně velikosti okna klienta aplikace vytvoříte úkol aplikace další pozadí generovat nové velikosti okna obrazu.Aplikace nevyžaduje těchto zprostředkujících obrazy; vyžaduje pouze obraz velikosti okna konečné.Chcete-li zabránit aplikaci z provádění této dodatečné práce můžete zrušit aktivního výkresu úkoly v obslužné rutiny zpráv pro WM_SIZE a WM_SIZING zprávy a poté kreslení přeplánovat práci po změně velikosti okna.

Chcete-li zrušit aktivní úkoly výkresu při změně velikosti okna, aplikace volá concurrency::task_group::cancel metoda v obslužné rutiny pro WM_SIZING a WM_SIZE zprávy.Obslužnou rutinu pro WM_SIZE zpráva také volání concurrency::task_group::wait metoda pro všechny aktivní úkoly dokončit a pak přeplánuje výkresu úkol pro velikost okna aktualizované čekat.

Zničení okna klienta, je vhodné zrušit aktivní úkoly výkresu.Zrušení aktivního výkresu úkoly zajišťuje pracovních podprocesů po zničení okna klienta Nevystavujte zprávy uživatelské rozhraní.Aplikace zruší aktivní úkoly obslužné rutiny pro kreslení WM_DESTROY zprávy.

Zrušení reagovat

CChildView::DrawMandelbrot Metodu, která provede úlohu výkresu musí reagovat na zrušení.Protože používá runtime zrušit úlohy zpracování výjimek CChildView::DrawMandelbrot metoda musí zaručit, že všechny prostředky jsou správně vyčištěný použití výjimky bezpečný mechanismus.V tomto příkladu Inicializace je získání prostředků (RAII) vzorek zaručit, že bitmapy bitů odemčené při úkolu je zrušena.

V aplikaci Mandelbrot přidat podporu pro zrušení

  1. V ChildView.h v protected část CChildView třídy, přidejte pro prohlášení OnSize, OnSizing, a OnDestroy zpráva funkce mapy.

    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. V ChildView.cpp, upravte mapu zprávu obsahující obslužné rutiny pro WM_SIZE, WM_SIZING, a WM_DESTROY zprávy.

    BEGIN_MESSAGE_MAP(CChildView, CWnd)
       ON_WM_PAINT()
       ON_WM_SIZE()
       ON_WM_SIZING()
       ON_WM_DESTROY()
    END_MESSAGE_MAP()
    
  3. Implementujte metodu CChildView::OnSizing. Tato metoda zruší stávající úkoly výkresu.

    void CChildView::OnSizing(UINT nSide, LPRECT lpRect)
    {
       // The window size is changing; cancel any existing drawing tasks.
       m_DrawingTasks.cancel();
    }
    
  4. Implementujte metodu CChildView::OnSize. Tato metoda zruší stávající výkresu úkoly a vytvoří nový úkol výkresu pro velikost okna aktualizovaný klient.

    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. Implementujte metodu CChildView::OnDestroy. Tato metoda zruší stávající úkoly výkresu.

    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. Definovat v ChildView.cpp, scope_guard třídy, která implementuje vzorek 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. Přidejte následující kód CChildView::DrawMandelbrot po volání metody 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);      
    });
    

    Tento kód zpracovává zrušení vytvořením scope_guard objektu.Obor opouštějí objekt odemkne bitů bitmapy.

  8. Změnit na konci CChildView::DrawMandelbrot metoda odvolat scope_guard objektu po bitů bitmapy jsou odemčené, ale před uživatelské rozhraní jsou odeslány žádné zprávy.Díky uživatelské rozhraní před odemčené bitů rastrový obrázek není aktualizován.

    // 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. Ověřte, zda byl úspěšně aktualizován aplikace vytvořením a spuštěním.

Při změně velikosti okna výkresu práce je provedena pouze velikost okna konečné.Žádné aktivní úkoly výkresu jsou zrušena také při okna zničen.

Top

Viz také

Koncepty

Funkční paralelismus (Concurrency Runtime)

Asynchronní bloky zpráv

Funkce usnadnění

Paralelní algoritmy

Zrušení v knihovně PPL

Další zdroje

Návody k Concurrency Runtime

Běžné aplikace knihovny MFC