description | title | ms.date | helpviewer_keywords | ms.assetid | ||
---|---|---|---|---|---|---|
Learn more about: Walkthrough: Removing Work from a User-Interface Thread |
Walkthrough: Removing Work from a User-Interface Thread |
08/19/2019 |
|
a4a65cc2-b3bc-4216-8fa8-90529491de02 |
This document demonstrates how to use the Concurrency Runtime to move the work that is performed by the user-interface (UI) thread in a Microsoft Foundation Classes (MFC) application to a worker thread. This document also demonstrates how to improve the performance of a lengthy drawing operation.
Removing work from the UI thread by offloading blocking operations, for example, drawing, to worker threads can improve the responsiveness of your application. This walkthrough uses a drawing routine that generates the Mandelbrot fractal to demonstrate a lengthy blocking operation. The generation of the Mandelbrot fractal is also a good candidate for parallelization because the computation of each pixel is independent of all other computations.
Read the following topics before you start this walkthrough:
We also recommend that you understand the basics of MFC application development and GDI+ before you start this walkthrough. For more information about MFC, see MFC Desktop Applications. For more information about GDI+, see GDI+.
This walkthrough contains the following sections:
This section describes how to create the basic MFC application.
-
Use the MFC Application Wizard to create an MFC application with all the default settings. See Walkthrough: Using the New MFC Shell Controls for instructions on how to open the wizard for your version of Visual Studio.
-
Type a name for the project, for example,
Mandelbrot
, and then click OK to display the MFC Application Wizard. -
In the Application Type pane, select Single document. Ensure that the Document/View architecture support check box is cleared.
-
Click Finish to create the project and close the MFC Application Wizard.
Verify that the application was created successfully by building and running it. To build the application, on the Build menu, click Build Solution. If the application builds successfully, run the application by clicking Start Debugging on the Debug menu.
This section describes how to draw the Mandelbrot fractal. This version draws the Mandelbrot fractal to a GDI+ Bitmap object and then copies the contents of that bitmap to the client window.
-
In pch.h (stdafx.h in Visual Studio 2017 and earlier), add the following
#include
directive:[!code-cppconcrt-mandelbrot#1]
-
In ChildView.h, after the
pragma
directive, define theBitmapPtr
type. TheBitmapPtr
type enables a pointer to aBitmap
object to be shared by multiple components. TheBitmap
object is deleted when it is no longer referenced by any component.[!code-cppconcrt-mandelbrot#2]
-
In ChildView.h, add the following code to the
protected
section of theCChildView
class:[!code-cppconcrt-mandelbrot#3]
-
In ChildView.cpp, comment out or remove the following lines.
[!code-cppconcrt-mandelbrot#4]
In Debug builds, this step prevents the application from using the
DEBUG_NEW
allocator, which is incompatible with GDI+. -
In ChildView.cpp, add a
using
directive to theGdiplus
namespace.[!code-cppconcrt-mandelbrot#5]
-
Add the following code to the constructor and destructor of the
CChildView
class to initialize and shut down GDI+.[!code-cppconcrt-mandelbrot#6]
-
Implement the
CChildView::DrawMandelbrot
method. This method draws the Mandelbrot fractal to the specifiedBitmap
object.[!code-cppconcrt-mandelbrot#7]
-
Implement the
CChildView::OnPaint
method. This method callsCChildView::DrawMandelbrot
and then copies the contents of theBitmap
object to the window.[!code-cppconcrt-mandelbrot#8]
-
Verify that the application was updated successfully by building and running it.
The following illustration shows the results of the Mandelbrot application.
Because the computation for each pixel is computationally expensive, the UI thread cannot process additional messages until the overall computation finishes. This could decrease responsiveness in the application. However, you can relieve this problem by removing work from the UI thread.
[Top]
This section shows how to remove the drawing work from the UI thread in the Mandelbrot application. By moving drawing work from the UI thread to a worker thread, the UI thread can process messages as the worker thread generates the image in the background.
The Concurrency Runtime provides three ways to run tasks: task groups, asynchronous agents, and lightweight tasks. Although you can use any one of these mechanisms to remove work from the UI thread, this example uses a concurrency::task_group object because task groups support cancellation. This walkthrough later uses cancellation to reduce the amount of work that is performed when the client window is resized, and to perform cleanup when the window is destroyed.
This example also uses a concurrency::unbounded_buffer object to enable the UI thread and the worker thread to communicate with each other. After the worker thread produces the image, it sends a pointer to the Bitmap
object to the unbounded_buffer
object and then posts a paint message to the UI thread. The UI thread then receives from the unbounded_buffer
object the Bitmap
object and draws it to the client window.
-
In pch.h (stdafx.h in Visual Studio 2017 and earlier), add the following
#include
directives:[!code-cppconcrt-mandelbrot#101]
-
In ChildView.h, add
task_group
andunbounded_buffer
member variables to theprotected
section of theCChildView
class. Thetask_group
object holds the tasks that perform drawing; theunbounded_buffer
object holds the completed Mandelbrot image.[!code-cppconcrt-mandelbrot#102]
-
In ChildView.cpp, add a
using
directive to theconcurrency
namespace.[!code-cppconcrt-mandelbrot#103]
-
In the
CChildView::DrawMandelbrot
method, after the call toBitmap::UnlockBits
, call the concurrency::send function to pass theBitmap
object to the UI thread. Then post a paint message to the UI thread and invalidate the client area.[!code-cppconcrt-mandelbrot#104]
-
Update the
CChildView::OnPaint
method to receive the updatedBitmap
object and draw the image to the client window.[!code-cppconcrt-mandelbrot#105]
The
CChildView::OnPaint
method creates a task to generate the Mandelbrot image if one does not exist in the message buffer. The message buffer will not contain aBitmap
object in cases such as the initial paint message and when another window is moved in front of the client window. -
Verify that the application was updated successfully by building and running it.
The UI is now more responsive because the drawing work is performed in the background.
[Top]
The generation of the Mandelbrot fractal is a good candidate for parallelization because the computation of each pixel is independent of all other computations. To parallelize the drawing procedure, convert the outer for
loop in the CChildView::DrawMandelbrot
method to a call to the concurrency::parallel_for algorithm, as follows.
[!code-cppconcrt-mandelbrot#301]
Because the computation of each bitmap element is independent, you do not have to synchronize the drawing operations that access the bitmap memory. This enables performance to scale as the number of available processors increases.
[Top]
This section describes how to handle window resizing and how to cancel any active drawing tasks when the window is destroyed.
The document Cancellation in the PPL explains how cancellation works in the runtime. Cancellation is cooperative; therefore, it does not occur immediately. To stop a canceled task, the runtime throws an internal exception during a subsequent call from the task into the runtime. The previous section shows how to use the parallel_for
algorithm to improve the performance of the drawing task. The call to parallel_for
enables the runtime to stop the task, and therefore enables cancellation to work.
The Mandelbrot application creates Bitmap
objects whose dimensions match the size of the client window. Every time the client window is resized, the application creates an additional background task to generate an image for the new window size. The application does not require these intermediate images; it requires only the image for the final window size. To prevent the application from performing this additional work, you can cancel any active drawing tasks in the message handlers for the WM_SIZE
and WM_SIZING
messages and then reschedule drawing work after the window is resized.
To cancel active drawing tasks when the window is resized, the application calls the concurrency::task_group::cancel method in the handlers for the WM_SIZING
and WM_SIZE
messages. The handler for the WM_SIZE
message also calls the concurrency::task_group::wait method to wait for all active tasks to complete and then reschedules the drawing task for the updated window size.
When the client window is destroyed, it is good practice to cancel any active drawing tasks. Canceling any active drawing tasks makes sure that worker threads do not post messages to the UI thread after the client window is destroyed. The application cancels any active drawing tasks in the handler for the WM_DESTROY
message.
The CChildView::DrawMandelbrot
method, which performs the drawing task, must respond to cancellation. Because the runtime uses exception handling to cancel tasks, the CChildView::DrawMandelbrot
method must use an exception-safe mechanism to guarantee that all resources are correctly cleaned-up. This example uses the Resource Acquisition Is Initialization (RAII) pattern to guarantee that the bitmap bits are unlocked when the task is canceled.
-
In ChildView.h, in the
protected
section of theCChildView
class, add declarations for theOnSize
,OnSizing
, andOnDestroy
message map functions.[!code-cppconcrt-mandelbrot#201]
-
In ChildView.cpp, modify the message map to contain handlers for the
WM_SIZE
,WM_SIZING
, andWM_DESTROY
messages.[!code-cppconcrt-mandelbrot#202]
-
Implement the
CChildView::OnSizing
method. This method cancels any existing drawing tasks.[!code-cppconcrt-mandelbrot#203]
-
Implement the
CChildView::OnSize
method. This method cancels any existing drawing tasks and creates a new drawing task for the updated client window size.[!code-cppconcrt-mandelbrot#204]
-
Implement the
CChildView::OnDestroy
method. This method cancels any existing drawing tasks.[!code-cppconcrt-mandelbrot#205]
-
In ChildView.cpp, define the
scope_guard
class, which implements the RAII pattern.[!code-cppconcrt-mandelbrot#206]
-
Add the following code to the
CChildView::DrawMandelbrot
method after the call toBitmap::LockBits
:[!code-cppconcrt-mandelbrot#207]
This code handles cancellation by creating a
scope_guard
object. When the object leaves scope, it unlocks the bitmap bits. -
Modify the end of the
CChildView::DrawMandelbrot
method to dismiss thescope_guard
object after the bitmap bits are unlocked, but before any messages are sent to the UI thread. This ensures that the UI thread is not updated before the bitmap bits are unlocked.[!code-cppconcrt-mandelbrot#208]
-
Verify that the application was updated successfully by building and running it.
When you resize the window, drawing work is performed only for the final window size. Any active drawing tasks are also canceled when the window is destroyed.
[Top]
Concurrency Runtime Walkthroughs
Task Parallelism
Asynchronous Message Blocks
Message Passing Functions
Parallel Algorithms
Cancellation in the PPL
MFC Desktop Applications