Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Managing UI Threads with CWinThread


The need to keep a user interface responsive at all times while simultaneously allowing for long-running tasks is something that comes up again and again in Windows development. Even with all of the improvements since Windows 16 in this area (particularly the availability of separate threads of execution), you may often find yourself needing to perform the tasks in the primary application thread. This may be due to legacy or third-party code that is not threadsafe (i.e., doesn't guard access to shared state). But your users demand that your application continue to be responsive even while these tasks execute. The question is then how to satisfy both needs without putting the tasks into background threads.

Fortunately, MFC comes to the rescue with “user interface threads.” These are managed in MFC with the CWinThread class. The CWinApp class that every MFC application has an instance of also derives from CwinThread, which provides for a message pump and handling message in the queue. In order to keep a user interface responsive, it’s necessary to periodically check the queue for available messages and dispatch them to the appropriate windows. If a task in the primary (CWinApp) CWinThread instance takes too much time, messages can back up in the queue; this is what causes an unresponsive application.

Creating additional CWinThreads is straightforward as long as you think through how to initialize and later terminate any of these objects. Usually this requires use of synchronization objects such as Win32 events (we covered these in previous newsletters). Let’s take a look at some code that demonstrates one possible approach for managing CWinThreads to keep the UI responsive.

First, we need to derive a class from CWinThread that will contain some value-added extensions we’ll need:

class CUIThread : public CWinThread
{
public:
	DECLARE_DYNCREATE(CUIThread)
	CUIThread();

// Attributes
public:
	HANDLE m_hEventKill;
	HANDLE m_hEventDead;

// Operations
public:
	void KillThread();

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CGDIThread)
	//}}AFX_VIRTUAL

// Implementation
public:
	virtual ~CUIThread();
	virtual void Delete();

protected:
	virtual BOOL InitInstance();

	// Generated message map functions
	//{{AFX_MSG(CUIThread)
		// NOTE - the ClassWizard will add and remove member functions here.
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()
};

Looking through this class, we see that there are two new member variables named “m_hEventKill” and “m_hEventDead”. The first variable is used to signal that the thread should kill itself (more on this in a bit), and the second variable is used to signal that the thread is really dead.

In addition, the virtual methods InitInstance and Delete are overridden from the base CWinThread class. The former is used to spin up the thread and the latter is used as a hook to know when the thread really dies. You won’t find Delete documented in the MSDN help documentation, but it does exist—just take a peek at the afxwin.h MFC file for more details on the layout of CWinThread.

Next, let’s take a look at some code for the new CUIThread class:

#include "stdafx.h"
#include "UIThread.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CUIThread

IMPLEMENT_DYNCREATE(CUIThread, CWinThread)

BEGIN_MESSAGE_MAP(CUIThread, CWinThread)
	//{{AFX_MSG_MAP(CUIThread)
		// NOTE - the ClassWizard will add and remove mapping macros here.
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CUIThread::CUIThread()
{
	m_bAutoDelete = FALSE;

	// kill event starts out in the signaled state
	m_hEventKill = CreateEvent(NULL, TRUE, FALSE, NULL);
	m_hEventDead = CreateEvent(NULL, TRUE, FALSE, NULL);
}

BOOL CUIThread::InitInstance()
{
	// wait for the kill notification.  service the 
// message queue.
	while (WaitForSingleObject(m_hEventKill, 0) == WAIT_TIMEOUT)
		;
	
	// avoid entering standard message loop by returning FALSE
	return FALSE;
}

void CUIThread::Delete()
{
	// calling the base here won't do anything but it is a 
// good habit
	CWinThread::Delete();

	// acknowledge receipt of kill notification
	VERIFY(SetEvent(m_hEventDead));
}

CUIThread::~CUIThread()
{
	CloseHandle(m_hEventKill);
	CloseHandle(m_hEventDead);
}

void CUIThread::KillThread()
{
	// Note: this function is called in the context of 
// other threads, not the thread itself.

	// reset the m_hEventKill which signals the thread 
// to shutdown
	VERIFY(SetEvent(m_hEventKill));

	// allow thread to run at higher priority during 
// kill process
	SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL);
	WaitForSingleObject(m_hEventDead, INFINITE);
	WaitForSingleObject(m_hThread, INFINITE);

	// now delete CWinThread object since no longer necessary
	delete this;
}

Let’s walk through the code a bit at this point. In the constructor, we mark the object so that it doesn’t auto-delete (this normally occurs when the thread goes away). We’re not allowing an auto-delete so that we can handle the destruction sequence a bit more manually. This isn’t required, but should give you an idea of what is possible. We also create two events that will be used to know when to kill the thread and when it’s actually dead.

Next, the InitInstance method goes into an infinite wait state on the kill event. This allows messages to be processed since the thread is not doing anything while waiting for the event to be signaled. When the kill event is finally signaled, the while loop is exited and the method returns FALSE to indicate that the thread is exiting. Kind of strange, but that’s how Microsoft designed this class to work.

Next, the Delete method calls the base method (which does nothing, but is good practice) and then signals the “dead” event. This tells other code in this class that the thread is really dead and not just exiting.

Finally, we’ll look at the KillThread method. This is really the main external method of the class and is what callers interact with. Once the thread is up and running, a user of this class would call the KillThread method to force the thread to exit gracefully. The first step is to signal the InitInstance method to stop waiting. It then boosts the thread priority slightly before going into a wait state on the “dead” event. Why boost the thread priority? Well, this is one of the few cases where I’ve had to consider changing thread priorities, but I’ve found that doing so allows the thread to exit more quickly than if the thread continues to run at its original priority. Usually I want the thread to terminate immediately, but given that it’s a thread, we might have to wait a bit for it to get a time slice from the OS. Next, we wait on the “dead” event to get signaled from the Delete method. Finally, we wait on the m_hThread handle to be signaled which means the thread has really gone away. Before we leave the method, we cleanup by deleting ourself since we had turned off auto-delete earlier.

To use this class we might do the following:

// create UI thread
	CUIThread* pUIThread = static_cast< CUIThread*>( AfxBeginThread(RUNTIME_CLASS(CUIThread)) );

// do something that takes a long time. . .

	// terminate the UI thread.
	pUIThread->KillThread();
 

Pretty simple. As you can see, it’s possible to integrate a UI thread into procedural code to allow your UI to continue to be responsive without resorting to more complex threading. I wouldn’t recommend you do this as a way around threading in general, but for special cases where it’s unavoidable, it may be your only option.


Mark M. Baker is the Chief of Research & Development at BNA Software located in Washington, D.C. [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.