If I call GetExitCodeThread for a thread that I know for sure has exited, why does it still say STILL_ACTIVE?

A customer reported that when they took the thread handle returned by the _beginthread function and passed it to Get­Exit­Code­Thread, the function reported that the thread was STILL_ACTIVE, even though the thread is known to have already exited.

The customer found this text in the documentation for _beginthread and wants more information about the case where the function can return an invalid handle if the thread exits quickly.

If the thread that's generated by _beginthread exits quickly, the handle that's returned to the caller of _beginthread might be invalid or point to another thread.

The point is that the _begin­thread function returns a handle to a thread, but that handle is valid only as long as the thread is running. Once the thread exits, the handle spontaneously becomes invalid, at which point it becomes eligible for recycling.

The upshot of this is that the handle returned by _begin­thread is useless, because it can go invalid for reasons outside your control.

The source code for the C runtime library is included with Visual Studio. You can read the source code for the _begin­thread function if you want to understand what's going on.

The customer asked whether this behavior of _begin­thread would explain the problem they are seeing.

Yes, it is exactly the problem you are seeing. Calling Get­Exit­Code­Thread with the handle returned by _begin­thread is useless.

  • If the thread is running, then you will get STILL_ACTIVE.
  • If the thread is not running, then you are using an invalid parameter and you will get garbage. The value STILL_ACTIVE is one possible manifestation of garbage.

In general, the return value from _begin­thread is not useful and should not be used for anything other than determining whether the thread was started successfully.

We recommend that if the customer wants to use the thread handle, they should switch to _begin­thread­ex and remember to close the handle when they are done.

The customer explained that their application was originally developed by another company. They had considered switching from _begin­thread to _begin­thread­ex, but didn't want to do so unless absolutely necessary, because they would have to justify the time and money required to fix the problem to their management.

Yes, switching from _begin­thread to _begin­thread­ex will fix the problem. As noted, the handle returned by _begin­thread is useless, and any code that tries to do anything beyond test it for success is like to run into problems exactly like the one you describe.

We learned about some people who want documentation that a bad idea is a bad idea. But that documentation is already there in MSDN. They already have the paperwork they need. I couldn't quite figure out what the customer was looking for. Did they just want a personalized version of the documentation customized just for them?

If that's what you need, you can copy/paste the following paragraph.

To whom it may concern,

It is my recommendation that the return value from the _begin­thread function should be used only to determine whether the function succeeded. In particular, the handle returned by the function is of indeterminate lifetime and cannot be reliably used.


Raymond Chen

Comments (9)
  1. SimonRev says:

    Here I was expecting the article to be about some thread that decided to return 253 as its exit code.

    1. poizan42 says:

      GetExitCode(Process|Thread) has always seemed badly designed to me. They could just have returned a DWORD instead with an error code including the possibility of ERROR_STILL_RUNNING or something like that. Instead we get a function returning a bool, setting the error with SetLastError() while returning the exitcode in an out parameter while making one possible value ambiguous.

      Also the documentation for the functions does not give the best advice. They advice you to not return 253. Better advice would be to always check whether a thread is running with WaitForSingleObject(hThread, 0) and only get the exit code if it has exited.

      1. Joshua says:

        To be fair your best advice is what I do. In continuation, I think the best way to check if running is WaitForSingleObject with zero timeout.

  2. R P (MSFT) says:

    That’s all well and good, but can I use the handle from _beginthread to get the thread’s exit code?

    1. Indeed, it is guaranteed not to work! Once the thread exits, the handle becomes invalid, so you can’t use it to get the exit code.

  3. Koro says:

    At this point, you might as well just change _beginthread to return a dummy invalid handle on success to force people to not rely on this value.

    1. Joshua says:

      Bad idea. I’ve come up with uses that are guaranteed to work. See DuplicateHandle for why it’s possible. (Note that you have to be synchronizing with the newly created thread somewhere or this is still racy).

  4. mikeb says:

    To be fair to the customer, the way the documentation for the return value of _beginthread is written, it makes it seems like there is an chance that using the handle could work, maybe with some effort.

    In my opinion, the documentation should include a direct statement of what you say in your article: “The return value from _begin­thread should not be used for anything other than determining whether the thread was started successfully”

    While the docs as written aren’t incorrect, without a clear, direct statement the documentation leaves the impression that there might be a way to use the handle safely. Why force users to figure this out for themselves and possibly get it wrong, when you can just say what you mean? (please keep in mind that the ‘you’ here refers to Microsoft, not Raymond).

  5. skSdnW says:

    Schrödinger’s handle. We know it was valid when we started but now, who knows. And we can’t even check.

Comments are closed.

Skip to main content