取消活动任务
Mandelbrot 应用程序创建的 Bitmap 对象的尺寸与客户端窗口大小一致。 每次调整客户端窗口的大小时,应用程序都会创建一个额外的后台任务,为新窗口大小生成一个图像。 应用程序不需要这些中间图像,只需要最终窗口大小的图像。 若要阻止应用程序执行这个额外的工作,可以在 WM_SIZE 和 WM_SIZING 消息的消息处理程序中取消任何活动的绘制任务,然后在调整窗口大小后重新计划绘制工作。
若要在调整窗口大小时取消活动的绘制任务,应用程序将在 WM_SIZING 和 WM_SIZE 消息的处理程序中调用 concurrency::task_group::cancel 方法。 WM_SIZE 消息的处理程序还将调用 concurrency::task_group::wait 方法以等待所有活动的任务完成,然后针对更新后的窗口大小重新计划绘制任务。
在销毁客户端窗口时,最好取消任何活动的绘制任务。 取消任何活动的绘制任务可确保工作线程在客户端窗口销毁后不会将消息发布到 UI 线程。 应用程序将取消 WM_DESTROY 消息的处理程序中的任何活动的绘制任务。
响应取消
执行绘制任务的 CChildView::DrawMandelbrot 方法必须响应取消。 由于运行时使用异常处理来取消任务,因此 CChildView::DrawMandelbrot 方法必须使用异常安全机制来保证正确地清理所有资源。 此示例使用了“资源获取初始化”(RAII) 模式,确保取消任务时位图位已解锁。
在 Mandelbrot 应用程序中添加对取消的支持
代码如下:
// 1. 在 ChildView.h 中,在 CChildView 类的 protected 部分中,
// 为 OnSize、OnSizing 和 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()
// 2. 在 ChildView.cpp 中,修改消息映射以包含 WM_SIZE、WM_SIZING 和
// WM_DESTROY 消息的处理程序。
BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_WM_PAINT()
ON_WM_SIZE()
ON_WM_SIZING()
ON_WM_DESTROY()
END_MESSAGE_MAP()
// 3. 实现 CChildView::OnSizing 方法。 此方法将取消任何现有绘制任务。
void CChildView::OnSizing(UINT nSide, LPRECT lpRect)
{
// The window size is changing; cancel any existing drawing tasks.
m_DrawingTasks.cancel();
}
//5. 实现 CChildView::OnSize 方法。 此方法将取消任何现有绘制任务,
// 并为更新后的客户端窗口大小创建新的绘制任务。
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)));
});
}
}
// 6. 实现 CChildView::OnDestroy 方法。 此方法将取消任何现有绘制任务。
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();
}
// 7. 在 ChildView.cpp 中,定义可实现 RAII 模式的 scope_guard 类。
// 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&);
};
// 8. 将以下代码添加到 CChildView::DrawMandelbrot 方法(在对 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);
});
// 9. 此代码通过创建 scope_guard 对象来处理取消。 当对象离开作用域时,它会解锁位图位。
// 修改 CChildView::DrawMandelbrot 方法的末尾,以在位图位解锁之后、
// 但在将任何消息发送到 UI 线程之前消除 scope_guard 对象。
// 这可确保在解锁位图位之前不会更新 UI 线程。
// 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);
// 通过生成并运行应用程序来验证其是否已成功更新。
当你调整窗口大小时,只会对最终窗口大小执行绘制工作。 在销毁窗口时,也会同时取消任何活动的绘制任务。