本文档演示了如何使用并发运行时将 Microsoft 基础类 (MFC) 应用程序中由用户界面 (UI) 线程执行的工作移动到工作线程。 本文档还演示了如何提高冗长绘制操作的性能。
通过将阻塞性操作(例如,绘制)卸载到工作线程来从 UI 线程中移除工作,可以提高应用程序的响应能力。本文使用可生成 Mandelbrot 分形的绘制例程来演示一个冗长的阻塞性操作。 Mandelbrot 分形的生成也非常适合并行化,因为每个像素的计算都是独立于所有其他计算的。
创建 Visual C++ MFC 应用程序
使用“MFC 应用程序向导”创建具有所有默认设置的 MFC 应用程序。 有关如何为 Visual Studio 版本打开向导的说明,请参阅演练:使用新的 MFC Shell 控件。
为项目键入一个名称,例如 Mandelbrot,然后单击“确定”以显示“MFC 应用程序向导”。
在“应用程序类型”窗格中,选择“单个文档”。 确保清除“文档/视图体系结构支持”复选框。
单击“完成”以创建项目并关闭“MFC 应用程序向导”。
通过生成并运行应用程序来验证其是否已成功创建。 若要生成应用程序,请在“生成”菜单上单击“生成解决方案”。 如果应用程序已成功生成,请单击“调试”菜单上的“开始调试”来运行应用程序。
实现 Mandelbrot 应用程序的串行版本
本部分介绍了如何绘制 Mandelbrot 分形。 此版本将 Mandelbrot 分形绘制到 GDI+ 位图对象,然后将该位图的内容复制到客户端窗口。下面的代码实现 Mandelbrot 应用程序的串行版本:
// 1.在 pch.h(在 Visual Studio 2017 及更早版本中为 stdafx.h)中,添加以下 #include 指令:
#include <memory>
// 2. 在 ChildView.h 中,在 pragma 指令后面定义 BitmapPtr 类型。
// BitmapPtr 类型允许使用一个指向要由多个组件共享的 Bitmap 对象的指针。
// 当任何组件都不再引用 Bitmap 对象时,该对象将被删除。
typedef std::shared_ptr<Gdiplus::Bitmap> BitmapPtr;
// 3. 在 ChildView.h 中,将以下代码添加到 CChildView 类的 protected 部分:
protected:
// Draws the Mandelbrot fractal to the specified Bitmap object.
void DrawMandelbrot(BitmapPtr);
protected:
ULONG_PTR m_gdiplusToken;
// 4. 在 ChildView.cpp 中,注释掉或移除以下行。
//#ifdef _DEBUG
//#define new DEBUG_NEW
//#endif
// 5. 在调试内部版本中,此步骤将阻止应用程序使用与 GDI+ 不兼容的 DEBUG_NEW 分配器。
// 在 ChildView.cpp 中,向 Gdiplus 命名空间添加一个 using 指令。
using namespace Gdiplus;
// 6. 将以下代码添加到 CChildView 类的构造函数和析构函数,以初始化和关闭 GDI+。
CChildView::CChildView()
{
// Initialize GDI+.
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
}
CChildView::~CChildView()
{
// Shutdown GDI+.
GdiplusShutdown(m_gdiplusToken);
}
// 7. 实现 CChildView::DrawMandelbrot 方法。
// 此方法会将 Mandelbrot 分形绘制到指定的 Bitmap 对象。
// 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);
}
// 7. 实现 CChildView::OnPaint 方法。 此方法将调用 CChildView::DrawMandelbrot,
// 然后将 Bitmap 对象的内容复制到窗口。
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);
}
}
// 8. 通过生成并运行应用程序来验证其是否已成功更新。
下图显示了 Mandelbrot 应用程序的结果。
由于每个像素的计算成本很高,因此在总体计算完成之前,UI 线程无法处理其他消息。 这可能会降低应用程序的响应能力。 但是,可以通过从 UI 线程中移除工作来缓解此问题。