CThread Class

Thread4.zip 3k release notes

This class makes it easier to use threads in an object oriented program. The difficulty with threads in object oriented design is that you have to provide a global static function as an entry point for the new thread. There are many classes available that provide answers to this problem, but CThread is a light and simple solution in MFC. CThread is used in the firstobject XML Messaging project. See also Multithreading and CMarkup.

A subthread is commonly used to perform a computationally intense activity or an activity that requires waiting. This subthread activity takes place independently of the main thread (primary user interface) of the application so as not to make the application unresponsive during the activity. Also, this subthread activity is often something that can be interrupted or stopped before completion if the user moves on or exits from the part of the program where the activity is needed. Example activities of this kind are repagination of a document, querying data to fill a window control, and listening to a TCP/IP port.

You usually make the CThread object a member of the window class that uses it, such as a frame window or dialog. The window class contains the code for the user interface in which the user is interacting in the main thread while the subthread performs its activity. The CThread object can also be a member of a utility class like it is in CSock and CMessageNode in the XML Messaging project. There it facilitates the independent networking duties of those classes.

The threads are distinguished from each other here as the main thread (or parent thread) and the subthread (or child thread). The main thread is the one that creates the subthread. Here are the public methods of CThread to be used in the main thread:

BOOL Start();
void Stop( BOOL bBlock = TRUE );
BOOL IsAlive();
void Signal();

Here are the internal methods of CThread to be used in the subthread:

BOOL Wait( DWORD dwMilliSecs = INFINITE );
BOOL WaitOnHandle( HANDLE hObject, DWORD dwMilliSecs = INFINITE );
BOOL IsExit();

Unlike most classes you use as members of your classes, CThread is an abstract class. It forces you to implement its (pure virtual) Main function. The best part about CThread is that it encapsulates a mechanism for allowing you to implement this function as just another function of your class that uses CThread.

Before getting into the details of setting up the CThread member, let's first illustrate what this means in an example implementation. The following dialog has a subthread to scan files while allowing the user to quit at any time. It just takes 2 simple functions in MyDialog.cpp to show you how easy it is to implement a subthread:

BOOL CMyDialog::OnInitDialog()
{
  CDialog::OnInitDialog();
  m_ScanThread.m_pDlg = this;
  m_ScanThread.Start();
  return TRUE;
}

UINT CMyDialog::ScanThread()
{
  while ( ! m_ScanThread.IsExit() )
  {
    // scan a file
  }
  return 0;
}

When the dialog is initialized, it starts the scan thread. As you might gather, the ScanThread method is the subthread function. Everything that takes place in that function is happening in the subthread. The subthread ends at the time of returning from that function. So it checks the result of m_ScanThread.IsExit to see if it is time yet to quit.

This scan thread is declared in MyDialog.h as follows:

#include "Thread.h"
class CMyDialog : public CDialog
{
  ...
  UINT ScanThread();
  class CScanThread : public CThread
  {
    CMyDialog* m_pDlg;
    virtual UINT Main() { return m_pDlg->ScanThread(); };
    friend class CMyDialog;
  };
  friend class CScanThread;
  CScanThread m_ScanThread;
  ...
};

The CScanThread class is declared and implemented within the declaration of CMyDialog! All it consists of is a member pointer to the dialog, an implementation of its Main function, and a friend declaration to allow the dialog's ScanThread member to access its protected CThread methods.

Even this small example would not be complete without explaining how to get information from the scan thread to the dialog. As mentioned earlier, the ScanThread function occurs within the scan subthread, and this includes any function called from ScanThread. Inside the scan thread you must obey all of the rules of a separate thread and not access any part of the dialog without proper synchronization. And you must never call any windows functions such as SetWindowText from the scan thread!

To get information from the subthread to the main thread, add data members to contain the information and declare a mutex to support synchronization of this data. For example, you might add the following to the CMyDialog declaration in MyDialog.h:

  CStringArray m_csaFiles;
  CMutex m_mutexFiles;

Whenever the scan thread discovers a file matching its scan criteria, it adds the file to the array safely by locking the mutex while modifying the array. This is how it would look inside the ScanThread function in MyDialog.cpp:

    m_mutexFiles.Lock();
    m_csaFiles.Add( csFile );
    m_mutexFiles.Unlock();

Similarly, whenever the dialog thread wants to look at the array it must lock the mutex while looking at it. Its that easy! However, there are some things to watch out for. Neither thread should perform any complex or time consuming actions while the mutex is locked because this can lead to conflicts. Also, if you are using multiple mutexes, avoid locking more than one at a time because this can result in deadlock.

You may want the subthread to be able to signal the main thread when it completes parts of its task. Instead of doing this, first consider implementing a timer (WM_TIMER) in your window class to check on the progress of the subthread periodically (like every half second). A timer eliminates many potential problems and is often the lightest and most natural solution. If the subthread must signal the main window thread, you should use PostMessage. Use the HWND handle of the window rather than the pointer (such as m_pDlg) in case your thread is stopped as part of the destructor of the window class.

To signal the subthread from the main thread, call the Signal method of the CThread object. This works together with the Wait methods of the CThread. The subthread can wait for this signal by testing the result of Wait rather than using the IsExit function used in the scan thread example. The Wait function tests for either the signal or the exit event and returns TRUE or FALSE respectively.

Examples of the use of CThread can be found in the firstobject XML Messaging project. Multithreading can improve the responsiveness of your Windows programs. Best wishes to you and your multithreaded projects!

 

comment posted How could main-thread call sub to PAUSE?

Cteng Ctsh 21-Jul-2005

This Article Is Very Clear & Useful! However, it does not make clear how we could:
(1)Set A Daemon Thread To Interact with GUI; &
(2)How to spawn child-worker thread from Daemon;

To get the subthread to pause, just create a flag member such as m_bPause and have the subthread check it regularly while it is performing its task. The tricky part is that when it pauses, the subthread must go into a wait state using the Wait method and will not resume until signaled by the main thread with the Signal method.

A daemon thread is another way of saying a subthread or worker thread, though it may tend to have a monitoring purpose such as watching a folder or port. With threading, no class or tool can substitute for your understanding of deadlock and race conditions (i.e. synchronization issues), so you must design your solution accordingly. To spawn a child worker thread from the daemon thread (i.e. a subthread from a subthread), there is nothing special to do. You can have a CThread member in your derived daemon thread class, and then start and stop it from within the daemon thread. Again, it is up to you to understand how all of the synchronization issues will apply to your design.

From the subthread you must not access the main GUI directly because this will create deadlock if the GUI happens to be making a call that depends on the subthread or is in the process of shutting down. Define a user message and call PostMessage to signal one of the Windows in the GUI such as your progress dialog. Then let the handler in the GUI (main thread) perform all of the GUI functions.

While it sounds very straight-forward, there is an exception to the rule of separation of data going on here that should be mentioned. PostMessage requires a Windows handle (when used as a CWnd method it automatically uses the CWnd member m_hWnd). You could have the main thread copy this handle to a subthread member before starting it up. But experience says it is okay to share the Windows handle itself between threads, just as it is okay to access the same mutex object from both threads, because the underlying data is just a thread-safe integer handle that is not being changed. But other than for CWnd::PostMessage and CMutex, always use a mutex to synchronize access to data that can be used by both threads at the same time.

If you want the subthread to wait while the GUI function is performed, call Wait which will allow the GUI (main thread) to either signal or to exit. It is important to have both options (service signal or exit signal) because the GUI may not get the posted message if it is in the process of shutting down, and the Wait method takes care of this (using Win32 WaitForMultipleObjects).

Modifying the above example, we can have the subthread only scan a folder when signaled by a Scan button on the dialog. The subthread notifies the main thread that it has completed with PostMessage. The Signal and Wait methods work together to take care of all the threading details, but you should also check IsExit() while scanning in case you need to abort before completing the scan.

#define WM_SCAN_COMPLETED (WM_APP)

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
  //{{AFX_MSG_MAP(CMyDialog)
  ON_BN_CLICKED(IDC_BUTTON_SCAN, OnButtonScan)
  ON_BN_CLICKED(IDC_BUTTON_PAUSE, OnButtonPause)
  //}}AFX_MSG_MAP
  ON_MESSAGE( WM_SCAN_COMPLETED, OnScanCompleted )
END_MESSAGE_MAP()

void CMyDialog::OnButtonScan() 
{
  m_mutexFlags.Lock();
  m_bPause = false;
  m_mutexFlags.Unlock();
  m_ScanThread.Signal();
  m_staticStatus.SetWindowText( "Scanning" );
  m_buttonScan.EnableWindow( FALSE );
  m_buttonPause.EnableWindow( TRUE );
}

void CMyDialog::OnButtonPause() 
{
  m_mutexFlags.Lock();
  m_bPause = true;
  m_mutexFlags.Unlock();
  m_buttonPause.EnableWindow( FALSE );
  m_buttonScan.EnableWindow( TRUE );
}

UINT CMyDialog::ScanThread()
{
  while ( m_ScanThread.Wait() )
  {
    bool bPause = false;

    // loop through files of folder
    while ( 1 )
    {
      // Check if pause
      m_mutexFlags.Lock();
      bPause = m_bPause;
      m_mutexFlags.Unlock();
      if ( bPause )
        break;

      // Check for exit
      if ( m_ScanThread.IsExit() )
        return 0;

      // scan a file
      // break if done scanning
    }
    if ( ! bPause )
      PostMessage( WM_SCAN_COMPLETED, 0, 0 ); // notify dialog
  }
  return 0;
}

LRESULT CMyDialog::OnScanCompleted( WPARAM wParam, LPARAM lParam )
{
  m_staticStatus.SetWindowText( "Scan Completed" );
  m_buttonPause.EnableWindow( FALSE );
  m_buttonScan.EnableWindow( TRUE );
  CStringArray csaFiles;
  m_mutexFiles.Lock();
  csaFiles.Append(m_csaFiles);
  m_csaFiles.RemoveAll();
  m_mutexFiles.Unlock();
  // Do something with csaFiles
}

Looking at this example, it would be very tempting to call m_staticStatus.SetWindowText from inside the ScanThread function, but remember that it is occuring inside the subthread and must not access the GUI directly. By using PostMessage you let the main thread take care of anything GUI related in the OnScanCompleted method.