YOU CAN CODE!

 

With The Case Of UCanCode.net  Release The Power OF  Visual C++ !   HomeProducts | PurchaseSupport | Downloads  
Download Evaluation
Pricing & Purchase?
E-XD++Visual C++/ MFC Products
Overview
Features Tour 
Electronic Form Solution
Visualization & HMI Solution
Power system HMI Solution
CAD Drawing and Printing Solution

Bar code labeling Solution
Workflow Solution

Coal industry HMI Solution
Instrumentation Gauge Solution

Report Printing Solution
Graphical modeling Solution
GIS mapping solution

Visio graphics solution
Industrial control SCADA &HMI Solution
BPM business process Solution

Industrial monitoring Solution
Flowchart and diagramming Solution
Organization Diagram Solution

Graphic editor Source Code
UML drawing editor Source Code
Map Diagramming Solution

Architectural Graphic Drawing Solution
Request Evaluation
Purchase
ActiveX COM Products
Overview
Download
Purchase
Technical Support
  General Q & A
Discussion Board
Contact Us

Links

Get Ready to Unleash the Power of UCanCode .NET

 


UCanCode Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!

"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"


VC++ MFC Thread Tutorial: _beginthreadex, WaitForSingleObject, pausing, resuming, and stopping threads

 
Arjay

Download Example Source Code

Abstract

Frequently in the Visual C++ forum and multithreaded forum, questions are raised on how to start and stop threads and how to manipulate MFC UI elements from within a worker thread. This article will take the reader through the steps of building a simple MFC dialog application called StartStop. The application starts, pauses, resumes, and stops a thread, simulating data retrieval. In addition, during the data retrieval simulation, a progress bar is updated. Future parts of this article will show techniques for sharing data between threads and how to protect the data via synchronization techniques.

Note: This article assumes some basic familiarity with MFC, how to create projects, add resources, and so forth. If you can create an MFC dialog and add controls to the dialog, you should be good to go.

Creating the Basic Project

Open up Visual Studio .Net 2003 and create an MFC dialog project called StartStop.

Because it's a good idea to keep the UI code separate from the threading code, you are going to put the threading code inside a ProgressMgr class, but first you are going to create the basic UI 'shell' code that creates the basic button handlers and UI variables that allow you to enable/disable the buttons, change the button text, and manipulate the progress control. You know, the UI stuff.

Create the UI

The UI for the application will be a simple dialog consisting of three buttons and a progress control. To create the UI, open the resource editor and click on the IDD_STARTSTOP_DIALOG. Next, delete the OK button and add two buttons with the ID of the first button ID_STARTPAUSERESUME and the second one as ID_STOP. Also, add a progress control. You should end up with something like Figure 1.

Figure 1

The plan will be to add some button and progress control variables, button message handlers, and some user-defined messages to let the UI to know when to update the progress control and when the thread has finished. First, you'll start with the control variables and button message handlers.

Add the UI control variables

Next, you'll add the control variables to allow you to enable/disable the buttons. Although many programmers use the Win32 GetDlgItem() API to connect the controls, it's preferred to leverage MFC's DDX mechanism and let MFC handle the plumbing of the controls. Once you've tried this method, you may never go back to GetDlgItem().

Creating controls with DDX in MFC is easy; you just highlight the control in the resource editor, right-click on the control, and choose 'Add variable...'. Here's how to do it for the Start button.

  1. Open the dialog in the resource editor.
  2. Right-click on the Start button and choose 'Add variable...'.
  3. The 'Add Member Variable Wizard bo?= StartStop' dialog will appear (see Figure 2).
  4. Under 'Variable name:', ENTER m_btnStartPauseResume.
  5. Press Finish.

Figure 2

Repeat for the Stop button and Progress control

Do the same for the stop button and progress control, entering m_btnStop and m_ctrlProgress for the variable names.

Add the Start and Stop button message handlers

After creating the control variables, in the dialog editor just double-click on the Start, Stop, and Cancel buttons to create the message handlers. You should end up with empty handlers as follows:

void CStartStopDlg::OnBnClickedStartPauseResume()
{
   // TODO: Add your control notification handler code here
}
void CStartStopDlg::OnBnClickedStop()
{
   // TODO: Add your control notification handler code here
}
void CStartStopDlg::OnBnClickedCancel()
{
   // TODO: Add your control notification handler code here
   OnCancel();
}

Toggling the Start/Pause button state

You want the start button to also function as a pause and resume button. To do this, you need to keep track of what 'mode' the button is in. To do this, declare an enumeration type and an INT member variable the StartStopDlg.h file. Don't forget to initialize this variable in the constructor to FALSE. You should end up with the following:

// Implementation (in StartStopDlg.h)

protected:
   enum ThreadStatus { TS_STOPPED, TS_START, TS_PAUSE, TS_RESUME };
   INT   m_ThreadState;

// CStartStopDlg dialog (in StartStopDlg.cpp)
CStartStopDlg::CStartStopDlg(CWnd* pParent    /*=NULL*/)
   : CDialog(CStartStopDlg::IDD, pParent)
   , m_ThreadState( TS_STOPPED )
{
   m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

Next, add a method to toggle the button display text and toggle state.

// Toggles the Start/Pause/Resume state and sets the button text
INT CStartStopDlg::ToggleSPRState( )
{
   if( TS_RESUME == m_ThreadState )
   {
      m_ThreadState = TS_START;
   }

   m_ThreadState++;

   CString sButtonText = _T("");

   switch( m_ThreadState )
   {
   case TS_START:
      sButtonText = _T("Pause");
      break;
   case TS_PAUSE:
      sButtonText = _T("Resume");
      break;
   case TS_RESUME:
      sButtonText = _T("Pause");
      break;
   default:
      ASSERT( 0 );    // We shouldn't reach this
   }

   // Set button text
   m_btnStartPause.SetWindowText( sButtonText );

   return m_ThreadState;
}

And a method to reset the StartPause button state and button text.

// Reset the start/pause button and state
void CStartStopDlg::ResetSPRState( )
{
   m_ThreadState = TS_STOPPED;
   m_btnStartPause.SetWindowText( _T("Start") );
   m_ctrlProgress.SetStep( 0 );
}

Stub Out the Start/Pause and Stop Button Handlers

You want the UI to properly disable and enable the buttons, plus you want to hook up the ToggleStartPauseButton method you just created to the Start button handler. Change the button handlers to the following:

void CStartStopDlg::OnBnClickedStartPauseResume()
{
   m_btnStartPause.EnableWindow( FALSE );
   m_btnStop.EnableWindow( FALSE );

   switch( ToggleSPRState( ) )
   {
   case TS_START:
      // NOTE: This is where the thread start method will go
      // For now, we are just going to stub it out with a sleep
      // call so we can test the UI part of the dialog
      Sleep( 5000 );
      break;
   case TS_PAUSE:
      // Pause the thread (stubbed out)
      Sleep( 5000 );
      break;
   case TS_RESUME:
      // Resume the thread (stubbed out)
      Sleep( 5000 );
      break;
   default
      ASSERT( 0 );    // We shouldn't reach this
   }

   m_btnStartPause.EnableWindow( TRUE );
   m_btnStop.EnableWindow( TRUE );
}


void CStartStopDlg::OnBnClickedStop()
{
   m_btnStop.EnableWindow( FALSE );
   m_btnStartPause.EnableWindow( FALSE );


   // NOTE: the thread stop method will go here. Stubbed out with
   // sleep to test the dialog
   Sleep( 1000 );

   m_btnStop.EnableWindow( TRUE );
   m_btnStartPause.EnableWindow( TRUE );

}

void CStartStopDlg::OnBnClickedCancel()
{
   // Stop thread goes here
   OnCancel();
}
Tip: You may wonder why the buttons have control variables that allow you to enable and disable the buttons inside the button handlers. It is true that you are going to only going to start, pause, and stop a thread and generally these operations are very quick, especially in your simple application. However, in a production application thread start up or shut down may be significant, so you want to disable the button (albeit temporarily) to let the user know that something is happening and also prevent users from 'double-clicking' on the button.

Test the Dialog

Test the dialog to make sure the start button toggles to pause properly and both start and stop buttons disable and enable properly.

Note: I took this screen shot after clicking the start button. Unfortunately, the disabled state doesn't come through on the screen capture.

Figure 3

Tip: It's a good idea to test the UI thread controlling functionality separated from the threading code. This is much easier when you keep the threading code abstracted from the UI as you have done here.

Finishing Up the UI

Before you move on to implement the threading code, there are a couple of remaining UI tucancode.net that you need to complete.

Initialize the progress control

For the progress control to work, you need to initialize it first. To do this, y ou simply call the CProgressDlg.SetRange32() method. You'll do this at the end of the OnInitDialog( ) method.

// CStartStopDlg message handlers
BOOL CStartStopDlg::OnInitDialog()
{
   CDialog::OnInitDialog();

   // Set the icon for this dialog. The framework does this
   // automatically when the application's main window is not a
   // dialog
   SetIcon(m_hIcon, TRUE);     // Set big icon
   SetIcon(m_hIcon, FALSE);    // Set small icon

   // TODO: get the max range from the ProgressMgr class,
   // for now just set the max to 1000
   m_ctrlProgress.SetRange32( 0, 1000 );
   m_ctrlProgress.SetPos( 0 );

   return TRUE;
}

Defining the user defined messages

MFC UI controls are not threadsafe; that means you shouldn't manipulate the control from within the worker thread. For example, you aren't allowed to call m_ctrlProgress.StepIt() from inside the worker thread. Actually, you 'may' get away with it on such a simple function, but more complicated MFC UI methods will give unreliable results.

Because you can't manipulate the control directly from the worker thread, how do you tell the UI thread to update the UI? Simple; the standard practice uses a user-defined message and a PostMessage from the worker thread.

Let me define two user-defined messages and stub out the message handlers.

  1. Open up the stdafx.h file and add:
    #define WM_USER_INC_PROGRESS     WM_USER + 1
    #define WM_USER_THREAD_COMPLETED WM_USER + 2
    Next
    
  2. Define the message handler prototypes. In CStartStopDlg.h, add:
    afx_msg LRESULT OnIncProgress( WPARAM, LPARAM );
    afx_msg LRESULT OnThreadCompleted( WPARAM, LPARAM );
    
    Important: Pay attention to the user-defined message signature because it has changed since VC6. In VC6, it used to be defined as afx_msg void OnMessageHandler( );. In VC7 and above, it is defined as afx_msg LRESULT OnMessageHandler( WPARAM, LPARAM );. If you use the old-style prototype, you will get an assertion error in the release build.
  3. Add the message map entries to the message map:
    BEGIN_MESSAGE_MAP(CSimpleThreadDlg, CDialog)
       ON_WM_PAINT()
       ON_WM_QUERYDRAGICON()
       //}}AFX_MSG_MAP
       ON_BN_CLICKED(ID_STARTSTOP, OnBnClickedStartstop)
       ON_BN_CLICKED(IDCANCEL, OnBnClickedCancel)
       ON_MESSAGE( WM_USER_INC_PROGRESS, OnIncProgress )
       ON_MESSAGE( WM_USER_THREAD_COMPLETED,
                   OnThreadCompleted )
    
    END_MESSAGE_MAP()
    
  4. Create the message handlers:
    // Message received from the worker thread to increment
    // the progress control
    LRESULT CStartStopDlg::OnIncProgress( WPARAM, LPARAM )
    {
       // Increment the progress control
       m_ctrlProgress.StepIt( );
       return 1;
    }
    
    // Message received from the worker thread.
    // Indicates the thread has completed
    LRESULT CStartStopDlg::OnThreadCompleted( WPARAM, LPARAM )
    {
       // Reset the start button text and state
       // (see code for implementation)
       ResetStartPauseButton( );
    
       //
       // TODO: add thread stop code
       //
       return 1;
    }
    

Creating the Thread Code

Now that the UI code has been stubbed out nicely (and tested), you can move on to the threading code. Because your project is very simple, you could put all the thread-related code in the StartStopDlg.cpp file. But instead of doing this, keep the threading code decoupled from the UI as much as possible. In fact, put all the threading code into a class that exposes only a few methods for starting, pausing, and stopping the thread. Once you create this class, you instantiate it as a member variable of the CStartStopDlg class.

Stubbing Out the CProgressMgr Class

Use the Add class wizard to create a new class. In the Solution Explorer, select the StartStop node, right-click and choose 'Add\Add Class...' The 'Add Class' dialog will appear. Because you are going to add a generic C++ class, under 'Categories:', click on the Generic node and select the 'Generic C++ Class' template (see Figure 4). Press Open.

Figure 4

Next, the 'Generic C++ Class Wizard appears. Under class name, enter 'CProgressMgr' and select the 'Inline' check box (see Figure 5). The inline option only creates a .h file without a .cpp file. Press the 'Finish' button. Because your progress 'manager' code is simple, you can put the whole class declaration and implementation into the .h file—the 'Inline' checkbox option does this for you.

Figure 5

The wizard creates a single ProgressMgr.h file containing:

#pragma once

class CProgressMgr
{
public:
   CProgressMgr(void)
   {
   }

   ~CProgressMgr(void)
   {
   }
};

Add the Start, Pause, and Stop method stubs

The UI is going to interact with this class to Start, Pause, and Stop the thread, so it's time to stub out these methods. Make the following changes to the CProgressMgr class.

#pragma once

class CProgressMgr
{
public:

   CProgressMgr( )
   {
   }

   ~CProgressMgr( )
   {
   }

// Public interface methods
public:
   HRESULT Pause( )           { Sleep( 1000 ); return S_OK; }
   HRESULT Resume( )          { Sleep( 1000 ); return S_OK; }
   HRESULT Start( HWND hWnd ) { Sleep( 1000 ); return S_OK; }
   HRESULT Stop( )            { Sleep( 1000 ); return S_OK; }
// Private fields
private:
   HWND   m_hWnd;    // Window handle to the UI dialog (used to
                     // send progress and completed messages)
};

The Start method takes a hWnd param. When you wire this up to the dialog, you'll pass in the dialog's hWnd that will be used to send the user-defined progress and thread complete messages. Notice that I've added Sleep( 1000 ) statements in the method stubs. This is so you can test the UI after connecting up the class methods. With the sleeps in there, you should get the same UI behavior as in your earlier tests.

Wiring Up the CProgressMgr Code to the UI

Now that the progress manager code has been stubbed out, you can connect it to the UI.

Add the #include ProgressMgr.h declaration

Modify the include section of theStartStopDlg.cpp and StartStop.cpp files. to add the #include ProgressMgr.h. You should end up with the following for StartStopDlg.cpp and StartStop.cpp files:

// StartStopDlg.cpp : implementation file
//

#include "stdafx.h"
#include "StartStop.h"
#include "ProgressMgr.h"
#include "StartStopDlg.h"
#include ".\startstopdlg.h"

Declare the CProgressMgr instance

Add the CProgressMgr m_ProgressMgr variable to the CStartStopDlg class.

// Implementation
protected:
   HICON        m_hIcon;
   BOOL         m_bToggleStartPause;
   CProgressMgr m_ProgressMgr;

Wire up the message handlers

Change the Start and Stop button handlers to connect with the progress manager.

void CStartStopDlg::OnBnClickedStartPauseResume()
{
   m_btnStartPause.EnableWindow( FALSE );
   m_btnStop.EnableWindow( FALSE );

   switch( ToggleSPRState( ) )
   {
   case TS_START:
      // Start the thread
      m_ProgressMgr.Start( GetSafeHwnd( ) );
      break;
   case TS_PAUSE:
      // Pause the thread
      m_ProgressMgr.Pause( );
      break;
   case TS_RESUME:
      // Resume the thread
      m_ProgressMgr.Resume( );
      break;
   default:
      ASSERT( 0 );    // We shouldn't reach this
   }

   m_btnStartPause.EnableWindow( TRUE );
   m_btnStop.EnableWindow( TRUE );
}

void CStartStopDlg::OnBnClickedStop()
{
   m_btnStartPause.EnableWindow( FALSE );
   m_btnStop.EnableWindow( FALSE );

   // Stop the thread
   m_ProgressMgr.Stop( );

   // Reset the startpause button
   ResetStartPauseButton( );

   m_btnStartPause.EnableWindow( TRUE );
   m_btnStop.EnableWindow( TRUE );
}

At this point, the UI code is completed. No more UI changes should be necessary.

Threading Preliminaries

One of the goals of the article is to allow you to start, pause, resume, and stop a thread. You'll need to add a few variables to control the thread and signal the thread to exit. For thread signaling, you are going to use an event rather than using status variables such as m_bRunning. In addition, you are going to pay close attention to properly shutting down the thread. Once you signal the thread to exit, you need to ensure that it has exited properly. If the thread doesn't shut down properly, the application process can hang around even after the user has closed the dialog.

Defining the thread-related variables

Define a couple of handles for the thread and the shutdown event. Modify the CProgressMgr class and add the following:

HANDLE      m_hThread;           // Secondary thread handle
HANDLE      m_hShutdownEvent;    // Shutdown Event handle
                                 // (causes thread to exit)

Initialize the member variables in the constructor

Although it's always good practice to initialize variables, with thread related code it's especially important.

CProgressMgr( )
   : m_hThread( NULL )
   , m_hShutdownEvent( ::CreateEvent( NULL, TRUE, FALSE, NULL ) )
{
}

Take care of thread cleanup

Modify the destructor to add the cleanup code. You'll define the ShutdownThread method next.

~CProgressMgr( )
{
   ShutdownThread( );
   ::CloseHandle( m_hShutdownEvent );
}

Shutdown thread implementation

The shutdown code simply checks whether the thread handle is null. If it isn't, it means the thread is running and signals the thread to exit by setting the ShutdownEvent and then waiting for the thread to exit. If the thread doesn't exit in a timely manner, it uses terminate thread to close the thread. In your sample code, you don't care if the thread gets terminated, but in a real app, you may want to check whether the thread was terminated. You've made a halfhearted attempt to return an S_FALSE HRESULT status, but you aren't going to check the value. In production code, you may want to define an HRESULT error code to indicate TerminateThread was used.

HRESULT ShutdownThread( )
{
   HRESULT hr = S_OK;

   // Close the worker thread
   if( NULL != m_hThread )
   {
      // Signal the thread to exit
      ::SetEvent( m_hShutdownEvent );

      // thread may be suspended, so resume before shutting down
      ::ResumeThread( m_hThread );

      // Wait for the thread to exit. If it doesn't shut down
      // on its own, force it closed with Terminate thread
      if ( WAIT_TIMEOUT == WaitForSingleObject( m_hThread, 1000 ) )
      {
         ::TerminateThread( m_hThread, -1000 );
         hr = S_FALSE;
      }

      // Close the handle and NULL it out
      ::CloseHandle( m_hThread );
      m_hThread = NULL;

   }

   // Reset the shutdown event
   ::ResetEvent( m_hShutdownEvent );

   return hr;
}

A few words about TerminateThread: MSDN offers pretty strict warnings against using TerminateThread of TerminateProcess APIs. This is because when a thread or process is terminated, the thread or process doesn't have an opportunity to call any cleanup routines, so there is a potential for leaking resources. See MSDN for more information.

In your use here, the thread proc is going to be a static method of the CProgressMgr class and your cleanup will occur even if you have to terminate the thread because the CProgressMgr destructor will get called when the dialog gets destructed. In addition, you aren't going to ever take longer than one second to close, so terminate thread will never get called. You've coded it this way to provide more of a production code example.

Create Thread implementation

Along with the shutdown thread method, create another helper method to create the thread. The third parameter is your ThreadProc. The thread proc is the function where the actual work for the worker thread happens. Notice that you pass the 'this' pointer during thread creation. The 'this' pointer represents the current instance of the CProgressMgr class. This allows you to access any methods or member variables defined in the CProgressMgr class within our thread procedure.

Note: To use _beginthreadex, be sure to add #include <process.h> to the stdafx.h file.
// Creates the secondary display thread
HRESULT CreateThread( )
{
   // Fire off the thread
   if( NULL == (m_hThread = (HANDLE)_beginthreadex(
      NULL,
      0,
      ThreadProc,
      static_cast<LPVOID>( this ),
      0,
      NULL) ) )
   {
      return HRESULT_FROM_WIN32( ::GetLastError( ) );
   }

   return S_OK;
}

Thread Procedure implementation

The thread procedure for this example is simple. All you want to do is simulate performing some work in the worker thread and updating the progress in the UI (via the method NotifyUI that internally uses PostMessage( ) to send post a message to the UI). However, even in its simplicity there are some good techniques to be had:

  1. Passing the 'this' pointer of the CProgressMgr class allows the thread proc to access class members.
  2. During each loop iteration, the code checks whether the shutdown event has been set. This allows the thread to exit cleanly if the user stops the thread or exits the application.
// Thread proc - where the worker thread code happens
static UINT WINAPI ThreadProc( LPVOID lpContext )
{
   CProgressMgr* pProgressMgr
      = reinterpret_cast< CProgressMgr* >( lpContext );

   // Loop to simulate a worker operation
   for( UINT uCount = 0; uCount < 100; uCount++ )
   {
      // Check if the shutdown event has been set,
      // if so exit the thread
      if( WAIT_OBJECT_0 ==
         WaitForSingleObject( pProgressMgr->GetShutdownEvent( ), 0 ) )
      {
         return 1;
      }

      // Send the progress message to the UI
      pProgressMgr->NotifyUI( NOTIFY_PROGRESS );

      // Sample code - delay the operation a bit
      Sleep( 75 );
   }

   // Send the thread completed message to the UI
   pProgressMgr->NotifyUI( NOTIFY_THREAD_COMPLETED );

   return 0;
}

Go to page

Building out the Start, Stop, Pause, and Resume methods

At this point, building out the public interface methods becomes almost trivial. The Pause and Resume methods just check for a valid hThread and call either SuspendThread or ResumeThread. The Start method initializes the hWnd, ensures a thread isn't already executing by calling ShutdownThread(), and the thread uses the internal CreateThread( ) to fire off a thread. Stop just calls your internal ShutdownThread method.

void Pause( )
{
   if( NULL != m_hThread )
   {
      ::SuspendThread( m_hThread );
   }
}

void Resume( )
{
   if( NULL != m_hThread )
   {
      ::ResumeThread( m_hThread );
   }
}

HRESULT Start( HWND hWnd )
{
   HRESULT hr = S_OK;

   m_hWnd = hWnd;

   if( SUCCEEDED( hr = ShutdownThread( ) ) )
   {
      hr = CreateThread( );
   }
   return hr;
}

HRESULT Stop( )
{
   return ShutdownThread( );
   }

Completing the Code

To complete the code, you just need to create the GetShutdownEvent and NotifyUI methods.

HANDLE& GetShutdownEvent( )
{
   return m_hShutdownEvent;
}

void NotifyUI( UINT uNotificationType )
{
   // Check if the hWnd is still valid before posting the message
   // Note: use PostMessage instead of SendMessage because
   // PostMessage performs an asynchronous post; whereas
   // SendMessage sends synchronously
   if( ::IsWindow( m_hWnd ) )
   {
      switch( uNotificationType )
      {
      case NOTIFY_INC_PROGRESS:
         ::PostMessage( m_hWnd, WM_USER_INC_PROGRESS, 0, 0 );
         break;
      case NOTIFY_THREAD_COMPLETED:
         ::PostMessage( m_hWnd, WM_USER_THREAD_COMPLETED, 0, 0 );
         break;
      default:
         ASSERT( 0 );
      }
   }
}

Program Operation

Press F5 to run the application in debug mode. Once the dialog opens, press the 'Start' button. You'll notice the button changes to 'Pause' and the progress bar begins to increment. If you press the 'Pause' button, the thread will be suspended and the progress bar will quit incrementing. Pressing 'Resume' will resume the secondary thread and the progress bar will continue to increment. When the thread has completed its simulation loop, it posts a thread completed message to the UI; this causes the UI to reset (in other words, the progress bar is cleared and the button text is set to 'Start'). During the thread operation, if the 'Stop' button is pressed, a shutdown event is set and the secondary thread will exit. Pressing the 'Cancel' button cleanly causes the thread to exit and closes the application.

 

 

Copyright ?1998-2024 UCanCode.Net Software , all rights reserved.
Other product and company names herein may be the trademarks of their respective owners.

Please direct your questions or comments to webmaster@ucancode.net