Aracılığıyla paylaş


İzlenecek yol: Kullanıcı Arabirimi İş Parçacığından İşi Kaldırma

Bu belgede, Microsoft Foundation Classes (MFC) uygulamasında kullanıcı arabirimi (UI) iş parçacığı tarafından gerçekleştirilen işi bir çalışan iş parçacığına taşımak için Eşzamanlılık Çalışma Zamanı'nın nasıl kullanılacağı gösterilmektedir. Bu belgede ayrıca uzun bir çizim işleminin performansının nasıl artırılası da gösterilmektedir.

Kullanıcı arabirimi iş parçacığından, örneğin çizim gibi engelleme işlemlerini çalışan iş parçacıklarına boşaltarak işi kaldırmak uygulamanızın yanıt hızını artırabilir. Bu izlenecek yol, uzun bir engelleme işlemini göstermek için Mandelbrot fraktalını oluşturan bir çizim yordamını kullanır. Mandelbrot fraktalının oluşturulması da paralelleştirme için iyi bir adaydır çünkü her pikselin hesaplaması diğer tüm hesaplamalardan bağımsızdır.

Ön koşullar

Bu kılavuza başlamadan önce aşağıdaki konuları okuyun:

Bu kılavuza başlamadan önce MFC uygulama geliştirme ve GDI+ ile ilgili temel bilgileri de anlamanız önerilir. MFC hakkında daha fazla bilgi için bkz . MFC Masaüstü Uygulamaları. GDI+ hakkında daha fazla bilgi için bkz . GDI+.

Bölümler

Bu izlenecek yol aşağıdaki bölümleri içerir:

MFC Uygulaması Oluşturma

Bu bölümde temel MFC uygulamasının nasıl oluşturulacağı açıklanmaktadır.

Visual C++ MFC uygulaması oluşturmak için

  1. Tüm varsayılan ayarlarla bir MFC uygulaması oluşturmak için MFC Uygulama Sihirbazı'nı kullanın. Visual Studio sürümünüz için sihirbazı açma yönergeleri için bkz . İzlenecek Yol: Yeni MFC Kabuğu Denetimlerini Kullanma.

  2. Proje için bir ad yazın (örneğin, Mandelbrot) ve ardından Tamam'a tıklayarak MFC Uygulama Sihirbazı'nı görüntüleyin.

  3. Uygulama Türü bölmesinde Tek belge'yi seçin. Belge/Görünüm mimarisi desteği onay kutusunun temizlendiğinden emin olun.

  4. Projeyi oluşturmak için Son'a tıklayın ve MFC Uygulama Sihirbazı'nı kapatın.

    Uygulamanın oluşturup çalıştırarak başarıyla oluşturulduğunu doğrulayın. Uygulamayı derlemek için, Derleme menüsünde Çözüm Oluştur'a tıklayın. Uygulama başarıyla derleniyorsa, Hata Ayıklama menüsünde Hata Ayıklamayı Başlat'a tıklayarak uygulamayı çalıştırın.

Mandelbrot Uygulamasının Seri Sürümünü Uygulama

Bu bölümde Mandelbrot fraktalını çizme açıklanmaktadır. Bu sürüm, Mandelbrot fraktalını bir GDI+ Bit Eşlem nesnesine çizer ve ardından bu bit eşlemin içeriğini istemci penceresine kopyalar.

Mandelbrot uygulamasının seri sürümünü uygulamak için

  1. pch.h dosyasında (Visual Studio 2017 ve önceki sürümlerde stdafx.h), aşağıdaki #include yönergeyi ekleyin:

    #include <memory>
    
  2. ChildView.h dosyasında yönergeden pragma sonra türünü tanımlayın BitmapPtr . türü, BitmapPtr bir Bitmap nesnenin işaretçisinin birden çok bileşen tarafından paylaşılabilmesini sağlar. Nesne Bitmap artık herhangi bir bileşen tarafından başvurulduğunda silinir.

    typedef std::shared_ptr<Gdiplus::Bitmap> BitmapPtr;
    
  3. ChildView.h dosyasında, sınıfın protected bölümüne CChildView aşağıdaki kodu ekleyin:

    protected:
       // Draws the Mandelbrot fractal to the specified Bitmap object.
       void DrawMandelbrot(BitmapPtr);
    
    protected:
       ULONG_PTR m_gdiplusToken;
    
  4. ChildView.cpp dosyasında, aşağıdaki satırları açıklama satırı yapın veya kaldırın.

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

    Hata ayıklama derlemelerinde, bu adım uygulamanın GDI+ ile uyumsuz olan ayırıcıyı DEBUG_NEW kullanmasını engeller.

  5. ChildView.cpp dosyasında ad alanına bir using yönerge Gdiplus ekleyin.

    using namespace Gdiplus;
    
  6. GDI+ uygulamasını başlatmak ve kapatmak için sınıfının oluşturucusunun ve yıkıcısının CChildView aşağıdaki kodu ekleyin.

    CChildView::CChildView()
    {
       // Initialize GDI+.
       GdiplusStartupInput gdiplusStartupInput;
       GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
    }
    
    CChildView::~CChildView()
    {
       // Shutdown GDI+.
       GdiplusShutdown(m_gdiplusToken);
    }
    
  7. CChildView::DrawMandelbrot yöntemini uygulayın. Bu yöntem Mandelbrot fraktalını belirtilen Bitmap nesneye çizer.

    // 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. CChildView::OnPaint yöntemini uygulayın. Bu yöntem çağırır CChildView::DrawMandelbrot ve nesnenin Bitmap içeriğini pencereye kopyalar.

    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. Uygulamanın oluşturup çalıştırarak başarıyla güncelleştirildiğini doğrulayın.

Aşağıdaki çizimde Mandelbrot uygulamasının sonuçları gösterilmektedir.

The Mandelbrot Application.

Her piksel için hesaplama işlem açısından pahalı olduğundan, genel hesaplama bitene kadar kullanıcı arabirimi iş parçacığı ek iletileri işleyemez. Bu, uygulamadaki yanıt hızını azaltabilir. Ancak, ui iş parçacığından iş kaldırarak bu sorunu giderebilirsiniz.

[Üst]

Ui İş Parçacığından Çalışma Kaldırma

Bu bölümde, Mandelbrot uygulamasındaki kullanıcı arabirimi iş parçacığından çizim çalışmasının nasıl kaldırılacağı gösterilmektedir. Çizim işini ui iş parçacığından bir çalışan iş parçacığına taşıyarak, kullanıcı iş parçacığı arka planda görüntü oluşturduğundan, ui iş parçacığı iletileri işleyebilir.

Eşzamanlılık Çalışma Zamanı görevleri çalıştırmak için üç yol sağlar: görev grupları, zaman uyumsuz aracılar ve basit görevler. Ui iş parçacığından işi kaldırmak için bu mekanizmalardan herhangi birini kullanabilirsiniz, ancak görev grupları iptali desteklediğinden bu örnekte eşzamanlılık::task_group nesnesi kullanılır. Bu kılavuz daha sonra, istemci penceresi yeniden boyutlandırıldığında gerçekleştirilen çalışma miktarını azaltmak ve pencere yok edildiğinde temizleme gerçekleştirmek için iptali kullanır.

Bu örnekte, kullanıcı arabirimi iş parçacığının ve çalışan iş parçacığının birbirleriyle iletişim kurmasını sağlamak için eşzamanlılık::unbounded_buffer nesnesi de kullanılır. Çalışan iş parçacığı görüntüyü ürettikkten sonra nesneye Bitmapunbounded_buffer bir işaretçi gönderir ve ardından ui iş parçacığına bir boya iletisi gönderir. Kullanıcı arabirimi iş parçacığı nesneden unbounded_buffer nesnesini Bitmap alır ve istemci penceresine çizer.

Çizim çalışmasını UI iş parçacığından kaldırmak için

  1. pch.h dosyasında (Visual Studio 2017 ve önceki sürümlerde stdafx.h), aşağıdaki #include yönergeleri ekleyin:

    #include <agents.h>
    #include <ppl.h>
    
  2. ChildView.h dosyasında, sınıfın protected bölümüne CChildView ve unbounded_buffer üye değişkenleri ekleyintask_group. nesne, task_group çizimi gerçekleştiren görevleri, nesne ise unbounded_buffer tamamlanmış Mandelbrot görüntüsünü tutar.

    concurrency::task_group m_DrawingTasks;
    concurrency::unbounded_buffer<BitmapPtr> m_MandelbrotImages;
    
  3. ChildView.cpp dosyasında ad alanına bir using yönerge concurrency ekleyin.

    using namespace concurrency;
    
  4. yönteminde CChildView::DrawMandelbrot çağrısından Bitmap::UnlockBitssonra concurrency::send işlevini çağırarak nesnesini UI iş parçacığına geçirinBitmap. Ardından kullanıcı arabirimi iş parçacığına bir boya iletisi gönderin ve istemci alanını geçersiz kılın.

    // 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. CChildView::OnPaint Güncelleştirilmiş Bitmap nesneyi almak için yöntemini güncelleştirin ve görüntüyü istemci penceresine çizin.

    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)));
          });
       }
    }
    

    yöntemi, CChildView::OnPaint ileti arabelleğinde yoksa Mandelbrot görüntüsünü oluşturmak için bir görev oluşturur. İleti arabelleği, ilk boya iletisi gibi durumlarda ve istemci penceresinin önüne başka bir pencere taşındığında bir Bitmap nesne içermez.

  6. Uygulamanın oluşturup çalıştırarak başarıyla güncelleştirildiğini doğrulayın.

Çizim çalışması arka planda gerçekleştirildiğinden kullanıcı arabirimi artık daha hızlı yanıt veriyor.

[Üst]

Çizim Performansını Geliştirme

Mandelbrot fraktalının oluşturulması paralelleştirme için iyi bir adaydır çünkü her pikselin hesaplaması diğer tüm hesaplamalardan bağımsızdır. Çizim yordamını paralelleştirmek için yöntemindeki dış for döngüye aşağıdaki gibi concurrency::p arallel_for algoritması çağrısına dönüştürün.CChildView::DrawMandelbrot

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

Her bit eşlem öğesinin hesaplaması bağımsız olduğundan, bit eşlem belleğine erişen çizim işlemlerini eşitlemeniz gerekmez. Bu, kullanılabilir işlemci sayısı arttıkça performansın ölçeklendirilmesini sağlar.

[Üst]

İptal Desteği Ekleme

Bu bölümde, pencere yeniden boyutlandırmanın nasıl işleneceğini ve pencere yok edildiğinde etkin çizim görevlerinin nasıl iptal edileceği açıklanmaktadır.

PPL'deki İptal belgesi, iptal işleminin çalışma zamanında nasıl çalıştığını açıklar. İptal işbirlikçidir; bu nedenle, hemen gerçekleşmez. İptal edilen bir görevi durdurmak için çalışma zamanı, görevden çalışma zamanına sonraki bir çağrı sırasında bir iç özel durum oluşturur. Önceki bölümde, çizim görevinin parallel_for performansını artırmak için algoritmanın nasıl kullanılacağı gösterilmektedir. çağrısı parallel_for , çalışma zamanının görevi durdurmasını ve bu nedenle iptal işleminin çalışmasını sağlar.

Etkin Görevleri İptal Etme

Mandelbrot uygulaması, boyutları istemci penceresinin boyutuyla eşleşen nesneler oluşturur Bitmap . İstemci penceresi her yeniden boyutlandırılırken, uygulama yeni pencere boyutu için bir görüntü oluşturmak üzere ek bir arka plan görevi oluşturur. Uygulama bu ara görüntüleri gerektirmez; yalnızca son pencere boyutu için görüntü gerektirir. Uygulamanın bu ek çalışmayı gerçekleştirmesini önlemek için ve WM_SIZING iletileri için WM_SIZE ileti işleyicilerindeki etkin çizim görevlerini iptal edebilir ve sonra pencere yeniden boyutlandırıldıktan sonra çizim çalışmasını yeniden zamanlayabilirsiniz.

Pencere yeniden boyutlandırıldığında etkin çizim görevlerini iptal etmek için WM_SIZING uygulama, ve WM_SIZE iletilerinin işleyicilerinde eşzamanlılık::task_group::cancel yöntemini çağırır. İletinin WM_SIZE işleyicisi de eşzamanlılık::task_group::wait yöntemini çağırarak tüm etkin görevlerin tamamlanmasını bekler ve ardından çizim görevini güncelleştirilmiş pencere boyutu için yeniden zamanlar.

İstemci penceresi yok edildiğinde, etkin çizim görevlerini iptal etmek iyi bir uygulamadır. Etkin çizim görevlerinin iptal edilmesi, istemci penceresi yok edildikten sonra çalışan iş parçacıklarının kullanıcı arabirimi iş parçacığına ileti göndermemesini sağlar. Uygulama, iletinin işleyicisindeki tüm etkin çizim görevlerini iptal eder WM_DESTROY .

İptale Yanıt Verme

CChildView::DrawMandelbrot Çizim görevini gerçekleştiren yöntemin iptale yanıt vermesi gerekir. Çalışma zamanı görevleri iptal etmek için özel durum işlemeyi kullandığından, yöntemin CChildView::DrawMandelbrot tüm kaynakların doğru şekilde temizlendiğinden emin olmak için özel durum açısından güvenli bir mekanizma kullanması gerekir. Bu örnekte, görev iptal edildiğinde bit eşlem bitlerinin kilidinin açılmasını sağlamak için Kaynak Alımı Başlatma (RAII) deseni kullanılır.

Mandelbrot uygulamasında iptal desteği eklemek için
  1. ChildView.h dosyasında, sınıfın protectedCChildView bölümünde , OnSizingve OnDestroy ileti eşleme işlevleri için OnSizebildirimler ekleyin.

    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. ChildView.cpp dosyasında, ileti eşlemesini , WM_SIZINGve WM_DESTROY iletileri için WM_SIZEişleyicileri içerecek şekilde değiştirin.

    BEGIN_MESSAGE_MAP(CChildView, CWnd)
       ON_WM_PAINT()
       ON_WM_SIZE()
       ON_WM_SIZING()
       ON_WM_DESTROY()
    END_MESSAGE_MAP()
    
  3. CChildView::OnSizing yöntemini uygulayın. Bu yöntem, mevcut tüm çizim görevlerini iptal eder.

    void CChildView::OnSizing(UINT nSide, LPRECT lpRect)
    {
       // The window size is changing; cancel any existing drawing tasks.
       m_DrawingTasks.cancel();
    }
    
  4. CChildView::OnSize yöntemini uygulayın. Bu yöntem mevcut tüm çizim görevlerini iptal eder ve güncelleştirilmiş istemci penceresi boyutu için yeni bir çizim görevi oluşturur.

    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. CChildView::OnDestroy yöntemini uygulayın. Bu yöntem, mevcut tüm çizim görevlerini iptal eder.

    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. ChildView.cpp dosyasında, RAII desenini scope_guard uygulayan sınıfını tanımlayın.

    // 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. çağrısından sonra yöntemine CChildView::DrawMandelbrotBitmap::LockBitsaşağıdaki kodu ekleyin:

    // 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);      
    });
    

    Bu kod bir scope_guard nesne oluşturarak iptali işler. Nesne kapsam dışına çıktığında bit eşlem bitlerinin kilidini açar.

  8. Bit eşlem bitlerinin kilidi açıldıktan sonra ancak ui iş parçacığına herhangi bir ileti gönderilmeden önce nesneyi kapatmak scope_guard için yönteminin sonunu CChildView::DrawMandelbrot değiştirin. Bu, bit eşlem bitlerinin kilidini açmadan önce UI iş parçacığının güncelleştirilmemesini sağlar.

    // 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. Uygulamanın oluşturup çalıştırarak başarıyla güncelleştirildiğini doğrulayın.

Pencereyi yeniden boyutlandırdığınızda, çizim çalışması yalnızca son pencere boyutu için gerçekleştirilir. Pencere yok edildiğinde tüm etkin çizim görevleri de iptal edilir.

[Üst]

Ayrıca bkz.

Eşzamanlılık Çalışma Zamanı İzlenecek Yollar
Görev Paralelliği
Zaman Uyumsuz İleti Blokları
İleti Geçirme İşlevleri
Paralel Algoritmalar
PPL'de İptal
MFC Masaüstü Uygulamaları