İ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.
Önkoş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
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.
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.Uygulama Türü bölmesinde Tek belge'yi seçin. Belge/Görünüm mimarisi desteği onay kutusunun temizlendiğinden emin olun.
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
pch.h dosyasında (Visual Studio 2017 ve önceki sürümlerde stdafx.h), aşağıdaki
#include
yönergeyi ekleyin:#include <memory>
ChildView.h dosyasında yönergeden
pragma
sonra türünü tanımlayınBitmapPtr
. türü,BitmapPtr
birBitmap
nesnenin işaretçisinin birden çok bileşen tarafından paylaşılabilmesini sağlar. NesneBitmap
artık herhangi bir bileşen tarafından başvurulduğunda silinir.typedef std::shared_ptr<Gdiplus::Bitmap> BitmapPtr;
ChildView.h dosyasında, sınıfın
protected
bölümüneCChildView
aşağıdaki kodu ekleyin:protected: // Draws the Mandelbrot fractal to the specified Bitmap object. void DrawMandelbrot(BitmapPtr); protected: ULONG_PTR m_gdiplusToken;
ChildView.cpp 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.ChildView.cpp'da ad alanına bir
using
yönergeGdiplus
ekleyin.using namespace Gdiplus;
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); }
CChildView::DrawMandelbrot
yöntemini uygulayın. Bu yöntem Mandelbrot fraktalını belirtilenBitmap
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); }
CChildView::OnPaint
yöntemini uygulayın. Bu yöntem çağırırCChildView::DrawMandelbrot
ve nesneninBitmap
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); } }
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.
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 Bitmap
unbounded_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
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>
ChildView.h dosyasında, sınıfın
protected
bölümüneCChildView
veunbounded_buffer
üye değişkenleri ekleyintask_group
. nesne,task_group
çizimi gerçekleştiren görevleri, nesne iseunbounded_buffer
tamamlanmış Mandelbrot görüntüsünü tutar.concurrency::task_group m_DrawingTasks; concurrency::unbounded_buffer<BitmapPtr> m_MandelbrotImages;
ChildView.cpp'da ad alanına bir
using
yönergeconcurrency
ekleyin.using namespace concurrency;
yönteminde
CChildView::DrawMandelbrot
çağrısındanBitmap::UnlockBits
sonra 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);
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 birBitmap
nesne içermez.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
ChildView.h dosyasında, sınıfın
protected
CChildView
bölümünde ,OnSizing
veOnDestroy
ileti eşleme işlevleri içinOnSize
bildirimler 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()
ChildView.cpp'da, ileti eşlemesini ,
WM_SIZING
veWM_DESTROY
iletileri içinWM_SIZE
iş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()
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(); }
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))); }); } }
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(); }
ChildView.cpp içinde, 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&); };
çağrısından sonra yöntemine
CChildView::DrawMandelbrot
Bitmap::LockBits
aş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.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 sonunuCChildView::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);
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ı