Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ

March 1996

Microsoft Systems Journal Homepage

Jeffrey Richter wrote Advanced Windows (Microsoft Press, 1995) and Windows 95: A Developer's Guide (M&T Books, 1995). Jeff is a consultant and teaches Win32-based programming seminars. He can be reached at v-jeffrr@microsoft.com.

Click to open or copy the KTTEST project files.

QI am working on an application that contains a user-interface thread and a worker thread. At some point the user-interface thread can be notified by the user that the work being done by the worker thread should be terminated. It seems to me that the easiest way to do this would be to call the TerminateThread function. But after reading the Win32® SDK documentation, I am concerned about the problems that this could cause. The documentation states "TerminateThread is a dangerous function that should only be used in the most extreme cases." When is it appropriate to use TerminateThread? Is it okay to use it just before terminating an application? What problems am I likely to run into?

Sam Malin

AAs a general rule, avoid calling TerminateThread. The documentation is correct when it points out several potential problems; it's worthwhile to delve into them more deeply.

The first problem is that the system does not free a thread's stack when the thread is terminated by TerminateThread. Other threads may be accessing values on the thread's stack and if the stack were freed, these other threads would raise access violations. (The first printing of my book, Advanced Windows, Microsoft Press, 1995, indicates on page 65 that Windows® 95 does free a thread's stack when that thread is terminated. This was true for early betas of Windows 95 but not for the final released version.) Of course, the terminated thread's stack will be freed when the process terminates, so your process will leak memory only while it is running.

The second problem with TerminateThread is that any DLLs mapped into the process's address space will not get notified. In general, you will have no way of knowing the specific consequences, but let's consider a few possibilities.

When a new thread is created, all DLLs currently mapped into the process get notified by a call to their DllEntryPoint (or DllMain) function with DLL_THREAD_ATTACH passed in the fdwReason parameter. A DLL should perform its thread-specific initialization when it receives this notification. When a thread terminates, all DLLs again get notified with DLL_THREAD_DETACH passed for the fdwReason parameter. This offers each DLL the opportunity to perform its thread-specific cleanup. However, when a thread is terminated with TerminateThread, the mapped DLLs do not get the DLL_THREAD_DETACH notification; therefore, these DLLs are not given an opportunity to perform the proper cleanup.

Some DLLs, such as the Microsoft® C run-time DLL (MSVCRT40.DLL), expect to receive the DLL_THREAD_DETACH notification. Microsoft's C run-time DLL uses this notification to free a block of memory. When it does not receive this notification because the thread is terminated with TerminateThread, this block of memory does not get freed until the process terminates. Another example would be a DLL that flushes data to a disk file when it receives the DLL_THREAD_DETACH notification. In this case, using TerminateThread will cause data loss.

The third problem with TerminateThread is that it allows critical sections and mutexes to be abandoned. If you don't immediately realize why this is very bad, one example should convince you. The multithreaded C run-time DLL uses critical sections to protect its internal data structures; for example, malloc and free use critical sections to gain access to heap structures. To prevent the heap from being corrupted by simultaneous access from multiple threads, the C run-time DLL uses critical sections.

Imagine that a thread calls malloc, which in turn calls EnterCriticalSection. When EnterCriticalSection returns, the thread owns the critical section. Now, all of a sudden, the thread is terminated by TerminateThread. At this point, the critical section is abandoned. When other threads in the process call malloc, the system will force these threads to wait until the critical section is released. But this will never happen-the threads that are waiting to execute malloc are suspended forever! This is a really good reason to avoid TerminateThread.

This problem is not limited to heap functions like malloc and free. Many functions in the C run-time DLL are not fully reentrant and therefore need to be protected with critical sections. So if you are using the C run-time DLL at all, it's difficult to avoid this problem.

Because of all this, I avoid TerminateThread like decaf cola. If you want to pull the rug out from under a thread (terminate it), the absolute best thing to do is to have the thread kill itself by calling ExitThread. The next question deals with this.

I would consider using TerminateThread only if I needed to terminate a thread that was doing something very destructive (for example, a thread purging records from a database or formatting a hard disk), and there was no other way for me to stop the thread.

One last note: many developers improperly use TerminateThread as part of a process's shutdown procedure. To terminate a process, any thread within the process can (and should) call ExitProcess.

QI have a graphics rendering application. It contains a user-interface thread that remains running for the duration of the process. When the user requests certain time-consuming graphics operations from the menu, a second thread is created to actually perform the operation. This keeps the user interface responsive. I also want to let the user cancel the requested operation. This means my user-interface thread must somehow terminate the worker thread. The source code for the worker thread is not under my control and making changes to it is difficult. Is there a good way to terminate my worker thread?

The information contained herein is published without the endorsement of Microsoft. Microsoft makes no warranty as to the accuracy or the completeness of the information. Readers are advised that they use this information at their own risk.

AI get this question a lot. Win32 does not offer a way to signal a worker thread to terminate unless the thread you want to terminate is running a message loop. If it is, you may be able to terminate the worker by executing the following line:

 PostThreadMessage(
dwThreadIdWorker, WM_QUIT, (WPARAM) nExitCode, 0);

Normally this will cause a message loop to exit, which in turn should cause the thread to exit. If you want the message sent with a higher priority and you have a handle to a window created by the worker thread, you can execute the following line:

 SendNotifyMessage(
hwnd, WM_QUIT, (WPARAM) nExitCode, 0);

The problem with using PostThreadMessage or SendNotifyMessage is that worker threads typically don't have message loops, so these functions are no help. The simplest thing to do in this case is to use a flag that is set by the user-interface thread and checked by the worker thread. For example you could create a global flag:

 volatile BOOL g_fStopWorkerThread = FALSE;

And have the worker thread check the flag from time to time :

 if (g_fStopWorkerThread)
ExitThread(dwExitCode);

Then from the user-interface thread you can set the flag:

 g_fStopWorkerThread = TRUE;

This approach is simple and it gives the worker thread control over when it exits. This is important because it prevents the thread from being terminated in the middle of an operation that should not be interrupted, such as a file update or a call to malloc. The major drawback is that you must sprinkle the worker thread's code with exit flag checks, unless there is a main processing loop where a single check can go. In your case, since it is difficult for you to modify the worker thread's code, you are unable to use this method.

The reader who asked the question also included the following additional details. Before the user-interface thread spawns the worker thread, the UI thread allocates two buffers-one for input, the other for output. Now, what about the buffers? What if the UI thread freed the buffers that it allocated while the worker thread was still running? Eventually the worker thread would attempt to reference its input or output buffer, at which point an access violation exception would be raised causing the thread to terminate. In effect, any time the worker thread referenced the buffers, it would also be checking for a request to terminate.

To use memory as a flag in this way, you must allocate and free the buffers using VirtualAlloc and VirtualFree to insure the memory is decommitted when freed-you cannot use malloc and free. Since you don't want the system to display a message box when the worker thread gets the accessviolation,youneed to guard the accesses to the buffers inside a structured exception handling __try block. If youcannotmodify the worker thread's code, you must create a wrapper function that calls the worker thread's function:

 DWORD WINAPI WorkerThreadWrapper (PVOID pvParam) {
DWORD dwExitCode = 0;
__try {
dwExitCode = WorkerThread(pvParam);
}
__except ((GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
}
return (dwExitCode);
}

This wrapper function allows the access violation to be caught without any changes to the worker function itself. As an added bonus, when the exception filter returns EXCEPTION_EXECUTE_HANDLER, the normal C++ exception-handling mechanism executes properly, causing the destructors to be called for all stack-based C++ objects created by the Worker Thread function. Because of this you need to be sure none of the destructors reference the freed buffers, or you will generate an exception from within a C++ destructor. This will result in a call to the C run-time DLL's terminate function which, by default, terminates the process (not just the thread).

After formulating this solution I thought about how I might make it more general. Freeing memory that a worker thread is using will eventually cause an exception to be raised in the worker thread. If I could find some mechanism to raise an exception in another thread, I would be able to terminate any worker thread without needing a buffer to free. Ideally I was looking for a Win32 API, perhaps called RaiseThreadException, that worked like RaiseException but added a thread handle parameter to specify the thread to raise the exception in. Since the system does not supply anything like this, I devised a way to implement it. I used this solution to create my own library of functions that allow a controlling thread to "cleanly" terminate a worker thread at any time (well, almost any time). This library is called KillThrd (see Figures 1 and 2). This approach will only work under Windows NTª. It does not work on Windows 95 for reasons that are too complex to delve into in this column.

RESOURCE.H

 //{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by KTTest.rc
//
#define IDD_KILLTHRDTEST                101
#define IDC_START                       1008
#define IDC_END                         1009
#define IDC_PROGRESS                    1015

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1016
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

Figure 4

The KTTest application's worker thread executes in several phases (described later); it adds an entry to the dialog box's list box as it enters each phase so you can observe the worker thread's progress. While the worker thread is running, you can click the "End worker thread" button which causes the application to call KillThrd_Kill to terminate the worker thread. You can see the various effects of calling KillThrd_Kill by clicking the "End worker thread" button during the worker thread's different phases.

KillThrd_Kill is asynchronous; it may return before the worker thread is actually terminated. If your application requires the controlling thread to wait until the worker thread has terminated, you can follow the call to KillThrd_Kill with this one line:

 WaitForSingleObject(pkt->m_hThread, INFINITE);

After the call to KillThrd_Kill, the UI thread calls KillThrd_Close to cleanup. The user of the KillThrd library is responsible for ensuring that all calls to KillThrd_CreateThread have a matching call to KillThrd_Close.

The code that implements the worker thread is a little unusual. It starts with the WorkerThread function, which implements an SEH __try block around a call to WorkerThreadWithoutSEH. When an exception is raised, the WorkerThreadExcFilter filter function is called. This exception filter watches for the SE_KILLTHREAD exception, which is raised by the KillThrd library functions. This exception is discussed in more detail below. For testing purposes, the WorkerThreadExcFilter function adds an entry to the list box when it detects the SE_KILLTHREAD exception but always returns EXCEPTION_CONTINUE_SEARCH. This behavior is unusual for filter functions, which usually return either EXCEPTION_CONTINUE_EXECUTION or EXCEPTION_EXECUTE_HANDLER when they recognize a specific exception.

WorkerThread's __try/__except block is nested inside a __try/__finally block. SEH allows arbitrary nesting. The __finally block is used to add another entry to the list box. This serves both as a notification that the thread is terminating, and as an indication that SEH is functioning properly with the KillThrd library functions.

WorkerThreadWithoutSEH is called by WorkerThread to do the real work. It creates a C++ object on the stack. The object's constructor and destructor each add entries to the list box. The destructor's list box entry serves both as a notification that the function is exiting, and as an indication that C++ exception handling is functioning properly. The rest of WorkerThreadWithoutSEH steps through each phase, adding an entry to the list box as each phase begins.

The first phase is "Sleep (3 seconds)." After adding the entry to the list box it executes:

 Sleep(3000);

This tests the termination of a sleeping thread. While the worker thread is sleeping, it cannot be killed. As soon as the thread awakes, it terminates immediately.

The second phase is "WaitForSingleObject (3 seconds)." After the list box is updated, the worker thread calls:

 WaitForSingleObject((HANDLE) TestClass, 3000);

Like Sleep, this phase tests the ability of the UI thread to cleanly kill the worker thread while it is waiting. The thread will not die until the wait completes.

The third phase, "WaitForMultipleObjects (3 seconds)," is a variation of the previous phase but using WaitForMultipleObjects.

 WaitForMultipleObjects(1, &h, TRUE, 3000);

Again, the worker will not die until the wait completes. The fourth phase is "Looping a lot, can't be broken." It executes the following:

 KillThrd_DelayDeath(TRUE);
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, "Looping a lot, can't be broken"));
for (int n = 0 ; n < 200000; n++) {
KillThrd_DelayDeath(TRUE);
free(malloc(10));
KillThrd_DelayDeath(FALSE);
}
KillThrd_DelayDeath(FALSE);

The first call to KillThrd_DelayDeath postpones the worker thread from being killed by KillThrd_Kill. An application would usually call KillThrd_DelayDeath to prevent being killed while performing an operation that shouldn't be interrupted such as a disk update. KillThrd_DelayDeath is the only KillThrd function called by the worker thread instead of the controlling thread. This function takes a BOOL parameter: TRUE to start protection, FALSE to end.

A DelayDeath count is maintained so multiple calls to KillThrd_DelayDeath(TRUE) are cumulative, and protection does not end until an equal number of calls to KillThrd_DelayDeath(FALSE) are made. This is important because it allows protected code to be written without concern for the current state of protection. When FALSE is passed in and the count reaches zero, a terminate flag (implemented as an event kernel object) is checked and, if a terminate request is pending, the worker thread terminates and the call to KillThrd_DelayDeath never returns.

The additional calls to KillThrd_DelayDeath inside the loop verify that the cumulative effects of calling KillThrd_DelayDeath are working properly. Passing TRUE causes the protection count to go up to 2. The calls to malloc and free are used because these are exactly the kinds of functions that you'd want to protect. Then KillThrd_DelayDeath(FALSE) brings the protection count down to 1. At no time while in the loop does the count reach 0-this loop cannot be interrupted by KillThread_Kill. Clicking "End worker thread" while in this phase will not result in the immediate termination of the worker thread; instead the request will remain pending until the loop terminates. When the loop terminates, KillThrd_DelayDeath(FALSE) is called one more time to match the call that preceded the loop. The protection count drops to zero, allowing the thread to be terminated if KillThrd_Kill was called.

The fifth and final phase is "Looping a lot, can be broken." After it updates the list box the worker thread enters a loop that should take a few seconds to complete. Unlike phase four, no calls to KillThread_DelayDeath are made to protect the loop from termination. This tests the immediate termination of a running thread. While in this phase, clicking "End worker thread" will terminate the thread without delay.

The WorkerMonitorThread function (in KTTEST.CPP) implements an additional thread to monitor the worker thread. Its parameter is a handle to the worker thread's kernel object, and it gets a handle to the dialog box by calling GetForegroundWindow. WaitForSingleObject is called to wait for the worker thread to terminate. (Thread objects become signaled when the thread terminates.) Some time after the thread terminates, the call to WaitForSingleObject returns with WAIT_OBJECT_0 and the monitor thread continues execution. Just before the monitor thread exits, it adds an entry to the list box, and fixes the states of the buttons.

Now let's turn to the KillThrd library itself, starting with the KillThrd_CreateThread function. The first call to KillThrd_CreateThread will reserve a thread local storage slot by calling TlsAlloc. This TLS slot will be used by the worker thread to store a pointer to its KILLTHRD_WORKERINFO. Then two structures are allocated: KILLTHRD_WORKERINFO for the worker thread and KILLTHRD for the controlling thread (a pointer to this block is KillThrd_CreateThread's return value). The KILLTHRD_WORKERINFO structure is initialized and three kernel objects are created (two mutexes and one event). I'll discuss these when I get to the code that uses them. Next the KILLTHRD structure is initialized. DuplicateHandle is used to create a new process-relative handle for each of the three kernel objects created, causing each object's usage count to be 2. The worker thread and the UI thread each get their own set of handles, thus avoiding the overhead required to coordinate the closing of a single set of handles shared by two threads.

Finally KillThrd_CreateThread uses _beginthreadex to start the worker thread. A wrapper function, KillThrd_ThreadFunc, is the starting address of the new thread; the worker function is not called directly.

The wrapper function sets up the exception handlers for the worker thread. The outer __try/_finally block ensures cleanup is performed when an exception other than SE_KILLTHREAD is raised in the worker thread. The inner __try/__except block handles the SE_KILLTHREAD exception. It's needed to prevent the system from popping up a message box to the user.

Inside the exception blocks the passed-in KILLTHRD_WORKERINFO structure pointer is stored in the reserved TLS slot for later use by KillThrd_DelayDeath. Finally the worker function (given as a parameter to KillThrd_CreateThread) is called and the worker starts working.

Now let's look at the KillThrd_Kill function, which implements the key technique of the KillThrd library. First it gets the m_hmtxControl mutex to prevent the worker thread from entering the KillThrd_DelayDeath function at the same time the controlling thread is executing KillThrd_Kill. If the worker cannot be killed immediately because it is delaying its death, the m_heventEnd event is set and it is assumed the thread will terminate itself when it calls KillThrd_DelayDeath(FALSE).

If the thread can be killed now, SuspendThread stops the worker thread. Then if the thread has not already terminated, GetThreadContext gets the worker thread's context. ContextFlags is set to CONTEXT_CONTROL to get a copy of the control registers, which includes the instruction pointer. The CONTEXT structure varies between CPU platforms. The PROGCTR macro, defined in KILLTHRD.CPP, accesses the instruction pointer member of CONTEXT. The instruction pointer is changed to point to KillThrd_ForceDeath; then the thread is resumed using ResumeThread. This is how you get the worker thread to jump to the KillThrd_ForceDeath function. If the worker thread is sleeping, waiting, or suspended, it will not continue execution until the system restarts the thread.

The KillThrd_ForceDeath function raises an SE_KILLTHREAD exception. The SEH mechanism will start calling exception filters looking for a handler; it should find the one set up by the KillThrd_ThreadFunc wrapper function:

 __except ((GetExceptionCode() == SE_KILLTHREAD) ? 
                            EXCEPTION_EXECUTE_HANDLER : 
                           EXCEPTION_CONTINUE_SEARCH) {
                     .
                    .
                    .
}

This is what you want to happen, but there is a problem under Windows 95. When you interrupt a worker thread that is servicing a system call on Windows 95, that thread may be in a state that causes only system exception handlers to be called while user exception handlers are skipped. When this is the case, the exception raised by KillThrd_ForceDeath is not offered to any of the user exception filters and none of the user __finally blocks are executed. When the system exception handler gets the SE_KILLTHREAD exception, it presents the user with the "This program has performed an illegal operation and will be shut down" message box and the application is terminated.

When KillThrd_ThreadFunc's exception filter returns EXCEPTION_EXECUTE_HANDLER, the SEH system will unwind the stack, calling any active __finally blocks and adjusting the thread's stack pointer. C++ exception handling will also proceed normally because it's implemented using SEH. Exception handling does expose one potential problem. It's possible to hang every application's user-interface on Windows 95 when a terminating thread that has been yanked from processing a system call proceeds to hang during exception handling.

The KillThrd library offers the following benefits over TerminateThread: proper exception handling in the terminating thread, DLL notification of thread termination, and freeing of the terminated thread's stack. It does not solve the problem of abandonment of critical sections by the C run-time DLL. It's too bad there is not a version of the C run-time that protects its critical sections with __try/__finally blocks. If you have the C run-time source, you could, of course, create such a version. But I don't recommend it because you'd have to redo it with each new version of the C run-time. Instead I suggest writing wrapper functions that use KillThrd_DelayDeath to protect C run-time calls.

If you are planning to use the KillThrd library, or the technique it presents, you should understand that it is impossible to evaluate the safety of terminating another thread unless you have specific knowledge of what that thread is doing. Let me say this as clearly as I can: misuse of these functions is dangerous, and can result in data loss or cause system instability. But KillThrd can be very useful when you need to terminate a worker thread.

A special thanks to Jim Harkins for his assistance with ideas and organization.

Have a question about programming in Win32? You can mail it directly to Win32 Q&A, Microsoft Systems Journal, 825 Eighth Avenue, 18th Floor, New York, New York 10019, or send it to MSJ (re: Win32 Q&A) via:

Internet:

Jeffrey Richter
v-jeffrr@microsoft.com

Eric Maffei
ericm@microsoft.com

From the March 1996 issue of Microsoft Systems Journal.

© 2016 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft