Click here to Skip to main content
Click here to Skip to main content
Go to top

SafeThread - A deadlock free worker thread

, 2 May 2004
Rate this:
Please Sign up or sign in to vote.
An article on a simple way to call User Interface methods from a worker thread, avoiding a common deadlock situation

Introduction

This article describes a method for preventing a deadlock that worker threads cause when calling User Interface methods. A deadlock occurs when the parent window thread is waiting for the worker thread to complete, while the worker thread is waiting for the parent window thread to handle its blocking User Interface call.

Background

The user should have a prior knowledge of multi-threading programming in Windows.

Implementation details

The implementation (below) waits for the thread to complete. When a user interface message is in the message queue it is being handled, in order to avoid the deadlock described above.

do 
{
  WaitResult = MsgWaitForMultipleObjects(1,     
    // only 1 wait object
                     m_hThread, // worker thread
                     FALSE,   // stop if any
                     INFINITE,  // no timeout
                     QS_ALLINPUT);
  if (WaitResult == WAIT_OBJECT_0 +1) 
  {
  // Handle windows message
  MSG Msg;
  BOOL IsMessage = ::PeekMessage( Msg,NULL,0,0,PM_REMOVE);
  if (IsMessage) 
  {
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  }
  }
} while (WaitResult == WAIT_OBJECT_0+1); 
ASSERT(WaitResult == WAIT_OBJECT_0);

Now, lets add another twist. Sometimes the parent user interface window would like to abort the thread immediately, and yet without any memory leaks. The implementation of the abort method raises a flag. The worker thread must call CheckAborted() periodically to check this flag.
The cleanest way to avoid memory leaks is by throwing an exception. The exception throwing mechanism cleanly destructs the objects in the stack. Objects allocated using the new operator are deleted in the destructor (of one of the objects in the stack) or by a Smart Pointer. You should not worry about catching the exception. CSafeThread has a wrapper of the thread procedure that catches this exception for you.
void CSafeThread::CheckAborted(DWORD WaitTime /* 
= 0 */)
{
  if (GetCurrentThread()!=NULL &&
    ::WaitForSingleObject(
     GetCurrentThread()->m_AbortEvent.m_hObject,WaitTime) == WAIT_OBJECT_0)
  {
  THROW(new CSafeThreadAbortException());
  }
}

The worker thread must also check the abort flag while it Sleeps or Waits. An implementation of WaitForSingleObject() and Sleep(), does just that. Below is the implementation of WaitForSingleObject().
 DWORD CSafeThread::WaitForSingleObject(HANDLE hHandle,
                     DWORD dwMilliseconds)
{
  CSafeThread* pThis = GetCurrentThread();
  if (pThis == NULL)
  {
  return ::WaitForSingleObject(hHandle,dwMilliseconds);
  }

  HANDLE Handles[2];
  Handles[0] = hHandle; 
  Handles[1] = m_AbortEvent.m_hObject; 
  DWORD WaitResult = ::WaitForMultipleObjects(2,
                        Handles, 
                        FALSE /* Wait Any */,
                        dwMilliseconds); 
  if (WaitResult == WAIT_OBJECT_0 + 1)
  {
  THROW(new CSafeThreadAbortException());
  }
  return WaitResult; 
}

In addition the CSafeThread implementation stores the handle of the parent window and provides an implementation to MessageBox. Calling this static method ensures that the parent window will be disabled when the message box appears.

Using the code

The example counts down from 10 to 0, displays a message box, and closes the dialog box. In order to start a new safe thread use the supplied BeginSafeThread static method. If you are going to use the MessageBox static method, do not forget to supply the last parent window argument. In the header file, you may use the SP_SafeThread tyepdef (SP stands for Smart Pointer) that will automatically delete the thread object when the dialog box is deleted. The SmartPtr class implementation was written by Sandu Turcan.

m_pThread =  CSafeThread::BeginSafeThread(CountThread, 
  //Thread Proc
                      this,    //Parameter
                      THREAD_PRIORITY_NORMAL, // Priority
                      0, // Stack Size
                      0, // Create Flags
                      0, // Security
                      this // Parent Window);

The implementation uses a helper static method that converts the parameter to this and calls the Count() method. The implementation of Count() changes the value of m_Counter (an MFC static text control) every one second.

UINT CSafeThreadExampleDlg::CountThread(void* pParam)
{ 
  CSafeThreadExampleDlg* pThis = 
    reinterpret_cast<CSafeThreadExampleDlg> (pParam);  
  pThis->Count(); 
  return 0; 
} 

void CSafeThreadExampleDlg::Count() 
{
  for (int i =10 ; i >=0 ; i--)
  {
  CString si;
  si.Format("%d",i);
  m_Counter.SetWindowText(si);
  CSafeThread::Sleep(1000);
  }
  CSafeThread::MessageBox("Countdown complete", // Message
              "SafeThreadExample"); // Title
  EndDialog(IDOK); //close dialog
}

The user interface of the example includes two buttons. The first one waits until the countdown completes, and the second one aborts the count down.

void CSafeThreadExampleDlg::OnBnClickedShutdown()
{
  m_pThread->SafeWaitForThread(); 
  OnOK(); //close dialog
} 

void CSafeThreadExampleDlg::OnBnClickedAbort() 
{
  m_pThread->SafeAbortThread();
  OnOK(); //close dialog 
}

Points of Interest

I am not sure that Microsoft MFC supports the workaround presented in this article. It worked for my project, and I would like to know here if it works also for you Smile | :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

itaifrenkel
Software Developer
Israel Israel
No Biography provided

Comments and Discussions

 
Generalabout dialog memory leaks PinmemberMember 365370720-Aug-08 20:14 
GeneralPlease help Me PinsussAnonymous1-Feb-05 9:56 
GeneralThank you PinsussAdrian ~ FoobarSoftware.com29-Nov-04 4:55 
Generalminor points PinsussAnonymous5-May-04 7:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 3 May 2004
Article Copyright 2004 by itaifrenkel
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid