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,
m_hThread,
FALSE,
INFINITE,
QS_ALLINPUT);
if (WaitResult == WAIT_OBJECT_0 +1)
{
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() p
eriodically 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 )
{
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 ,
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,
this,
THREAD_PRIORITY_NORMAL,
0,
0,
0,
this
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",
"SafeThreadExample");
EndDialog(IDOK);
}
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();
}
void CSafeThreadExampleDlg::OnBnClickedAbort()
{
m_pThread->SafeAbortThread();
OnOK();
}
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 :)
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.