V720. The 'SuspendThread' function is usually used when developing a debugger. See documentation for details.
- Why you should never suspend a thread
- The SuspendThread function suspends a thread, but it does so asynchronously
The analyzer has detected that the SuspendThread() or Wow64SuspendThread() function is used in the program. Calling these functions is in itself not an error. But developers tend to use them inappropriately. It may result in the program's misbehavior.
The SuspendThread() function is designed to assist the development of debuggers and other similar applications. If you use this function in your application for syncing tasks, it's highly probable that your program contains an error.
The problem with the misuse of the SuspendThread() function is discussed in the following articles:
- Why you should never suspend a thread.
- The SuspendThread function suspends a thread, but it does so asynchronously.
Please read them. If you find that the SuspendThread() function is used incorrectly in your code, then you need to rewrite it. If everything is OK, simply turn off the V720 diagnostic in the analyzer's settings.
Articles published on the Internet sometimes disappear or change their location. Therefore, we cite the text of both articles in the documentation, just in case.
Why you should never suspend a thread
It's almost as bad as terminating a thread.
Instead of just answering a question, I'm going to ask you the questions and see if you can come up with the answers.
Consider the following program, in (gasp) C#:
using System.Threading;
using SC = System.Console;
class Program {
public static void Main() {
Thread t = new Thread(new ThreadStart(Program.worker));
t.Start();
SC.WriteLine("Press Enter to suspend");
SC.ReadLine();
t.Suspend();
SC.WriteLine("Press Enter to resume");
SC.ReadLine();
t.Resume();
}
static void worker() {
for (;;) SC.Write("{0}\r", System.DateTime.Now);
}
}
When you run this program and hit Enter to suspend, the program hangs. But if you change the worker function to just "for(;;) {}" the program runs fine. Let's see if we can figure out why.
The worker thread spends nearly all its time calling System.Console.WriteLine, so when you call Thread.Suspend(), the worker thread is almost certainly inside the System.Console.WriteLine code.
Q: Is the System.Console.WriteLine method threadsafe?
Okay, I'll answer this one: Yes. I didn't even have to look at any documentation to figure this out. This program calls it from two different threads without any synchronization, so it had better be threadsafe or we would be in a lot of trouble already even before we get around to suspending the thread.
Q: How does one typically make an object threadsafe?
Q: What is the result of suspending a thread in the middle of a threadsafe operation?
Q: What happens if - subsequently - you try to access that same object (in this case, the console) from another thread?
These results are not specific to C#. The same logic applies to Win32 or any other threading model. In Win32, the process heap is a threadsafe object, and since it's hard to do very much in Win32 at all without accessing the heap, suspending a thread in Win32 has a very high chance of deadlocking your process.
So why is there even a SuspendThread function in the first place?
Debuggers use it to freeze all the threads in a process while you are debugging it. Debuggers can also use it to freeze all but one thread in a process, so you can focus on just one thread at a time. This doesn't create deadlocks in the debugger since the debugger is a separate process.
The SuspendThread function suspends a thread, but it does so asynchronously
Okay, so a colleague decided to ignore that advice because he was running some experiments with thread safety and interlocked operations, and suspending a thread was a convenient way to open up race windows.
While running these experiments, he observed some strange behavior.
LONG lValue;
DWORD CALLBACK IncrementerThread(void *)
{
while (1) {
InterlockedIncrement(&lValue);
}
return 0;
}
// This is just a test app, so we will abort() if anything
// happens we don't like.
int __cdecl main(int, char **)
{
DWORD id;
HANDLE thread = CreateThread(NULL, 0, IncrementerThread, NULL, 0,
&id);
if (thread == NULL) abort();
while (1) {
if (SuspendThread(thread) == (DWORD)-1) abort();
if (InterlockedOr(&lValue, 0) != InterlockedOr(&lValue, 0))
{
printf("Huh? The variable lValue was modified by a suspended
thread?\n");
}
ResumeThread(thread);
}
return 0;
}
The strange thing is that the "Huh?" message was being printed. How can a suspended thread modify a variable? Is there some way that InterlockedIncrement can start incrementing a variable, then get suspended, and somehow finish the increment later?
The answer is simpler than that. The SuspendThread function tells the scheduler to suspend the thread but does not wait for an acknowledgment from the scheduler that the suspension has actually occurred. This is sort of alluded to in the documentation for SuspendThread which says:
This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization.
You are not supposed to use SuspendThread to synchronize two threads because there is no actual synchronization guarantee. What is happening is that the SuspendThread signals the scheduler to suspend the thread and returns immediately. If the scheduler is busy doing something else, it may not be able to handle the suspend request immediately, so the thread being suspended gets to run on borrowed time until the scheduler gets around to processing the suspend request, at which point it actually gets suspended.
If you want to make sure the thread really is suspended, you need to perform a synchronous operation that is dependent on the fact that the thread is suspended. This forces the suspend request to be processed since it is a prerequisite for your operation, and since your operation is synchronous, you know that by the time it returns, the suspend has definitely occurred.
The traditional way of doing this is to call GetThreadContext, since this requires the kernel to read from the context of the suspended thread, which has as a prerequisite that the context be saved in the first place, which has as a prerequisite that the thread be suspended.