Raymond Chen explains that COM STA threads are required to pump messages only when idle; code that is always busy doesn't need an explicit message loop, but COM still creates a hidden window that requires pumping when the thread becomes idle to avoid jamming window broadcasts.
<p>One of the rules for COM single-threaded apartments (STA) is that the thread in that apartment must pump messages. But we also see code that initializes COM in single-threaded mode but which never pumps messages. Consider <a href="https://github.com/microsoft/Windows-classic-samples/blob/adf93f04125e0b5bd84ad0a9a073f7fea993a360/Samples/MSXML/DynamDom/cpp/dynamDOM.cpp#L271C1-L280C2"> this function</a> from the <a href="https://github.com/microsoft/Windows-classic-samples/blob/adf93f04125e0b5bd84ad0a9a073f7fea993a360/Samples/MSXML/DynamDom/README.md"> XML DOM object dynamic creation sample</a>:</p>
<pre>int __cdecl wmain()
{
HRESULT hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
dynamDOM();
CoUninitialize();
}
return 0;
}
</pre>
<p>The <code>CoInitialize</code> function initializes COM in single-threaded apartment mode, and then the program does some work, and then it uninitializes COM, and it <i>never pumps messages</i>. What gives? Shouldn’t there be a message loop?</p>
<p>The rule about single-threaded apartments is that they must pump messages <i>when idle</i>. If they are busy doing something, then clearly they can’t pump messages because they are busy doing something!¹</p>
<p>If your thread initializes COM as a single-threaded apartment, and then does a bunch of work, and then uninitializes COM, then that’s great. Your thread was never idle, so it never got a chance to pump messages. (Though if your thread made COM calls out to other threads, COM will pump messages while waiting for the reply, so it did pump messages while the thread was idle.)</p>
<p>Failing to pump messages when idle means that when another thread wants to communicate with your thread, it never gets a response. Now, if your thread is busy, then it’s fine that the other thread doesn’t get a response from you—you’re busy with something else after all. But if you are in a single-threaded COM apartment and you have finished with whatever you’re doing, you need to pump messages to see if there’s any work that COM wants you to do, or you need to uninitialize COM.</p>
<p>Now, you might say, “Look, my thread doesn’t create any windows, and it doesn’t do any cross-thread COM stuff, so who cares that it’s not pumping messages? It’s not like anybody is ever going to ask this thread to do anything, and since it created no windows, nobody could send it anything.”</p>
<p>Aha, but you see, your thread <i>did</i> create a window. When you initialize a thread as a single-threaded apartment, <u>COM</u> creates a window. It creates this window so that it can receive inbound requests for the thread to do something. If you don’t pump messages, then you have a thread blocked not pumping messages, which will jam up window broadcasts.</p>
<p>¹ An intentionally obtuse interpretation of the rule that “an STA thread must pump messages” would be that your thread can’t do anything except call <code>GetMessage</code> and <code>DispatchMessage</code>! Because any other line of code would not be “pumping messages”.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260522-00/?p=112348">Why do you say that a COM STA thread must pump messages if I see sample code creating STA threads and not pumping messages?</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
# Why do you say that a COM STA thread must pump messages if I see sample code creating STA threads and not pumping messages? - The Old New Thing
Source: [https://devblogs.microsoft.com/oldnewthing/20260522-00?p=112348](https://devblogs.microsoft.com/oldnewthing/20260522-00?p=112348)
One of the rules for COM single\-threaded apartments \(STA\) is that the thread in that apartment must pump messages\. But we also see code that initializes COM in single\-threaded mode but which never pumps messages\. Consider[this function](https://github.com/microsoft/Windows-classic-samples/blob/adf93f04125e0b5bd84ad0a9a073f7fea993a360/Samples/MSXML/DynamDom/cpp/dynamDOM.cpp#L271C1-L280C2)from the[XML DOM object dynamic creation sample](https://github.com/microsoft/Windows-classic-samples/blob/adf93f04125e0b5bd84ad0a9a073f7fea993a360/Samples/MSXML/DynamDom/README.md):
```
int __cdecl wmain()
{
HRESULT hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
dynamDOM();
CoUninitialize();
}
return 0;
}
```
The`CoInitialize`function initializes COM in single\-threaded apartment mode, and then the program does some work, and then it uninitializes COM, and it*never pumps messages*\. What gives? Shouldn’t there be a message loop?
The rule about single\-threaded apartments is that they must pump messages*when idle*\. If they are busy doing something, then clearly they can’t pump messages because they are busy doing something\!¹
If your thread initializes COM as a single\-threaded apartment, and then does a bunch of work, and then uninitializes COM, then that’s great\. Your thread was never idle, so it never got a chance to pump messages\. \(Though if your thread made COM calls out to other threads, COM will pump messages while waiting for the reply, so it did pump messages while the thread was idle\.\)
Failing to pump messages when idle means that when another thread wants to communicate with your thread, it never gets a response\. Now, if your thread is busy, then it’s fine that the other thread doesn’t get a response from you—you’re busy with something else after all\. But if you are in a single\-threaded COM apartment and you have finished with whatever you’re doing, you need to pump messages to see if there’s any work that COM wants you to do, or you need to uninitialize COM\.
Now, you might say, “Look, my thread doesn’t create any windows, and it doesn’t do any cross\-thread COM stuff, so who cares that it’s not pumping messages? It’s not like anybody is ever going to ask this thread to do anything, and since it created no windows, nobody could send it anything\.”
Aha, but you see, your thread*did*create a window\. When you initialize a thread as a single\-threaded apartment,COMcreates a window\. It creates this window so that it can receive inbound requests for the thread to do something\. If you don’t pump messages, then you have a thread blocked not pumping messages, which will jam up window broadcasts\.
¹ An intentionally obtuse interpretation of the rule that “an STA thread must pump messages” would be that your thread can’t do anything except call`GetMessage`and`DispatchMessage`\! Because any other line of code would not be “pumping messages”\.
### Category
### Topics
## Author

Raymond has been involved in the evolution of Windows for more than 30 years\. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie\-jeebies\. The Web site spawned a book, coincidentally also titled The Old New Thing \(Addison Wesley 2007\)\. He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information\.
This article from Microsoft's Old New Thing blog explains the rationale behind best practices for Windows kernel callback functions, particularly why blocking or waiting on work items defeats their purpose, using a cautionary tale about drivers causing system hangs.
A debugging story about a Windows program hanging when the user changes keyboard layouts due to a background thread that created a window but wasn't pumping messages. The fix is to either pump messages or destroy the window.
This article from The Old New Thing explains that Windows thread pools are optimized for throughput, not latency, and provides solutions for low-latency scheduling, such as creating a custom thread pool or using a dedicated worker thread, with code examples in C++ and C#.
Raymond Chen explains why C++/WinRT does not allow multiple awaits on asynchronous operations like C#, JavaScript, and Python do, citing the lack of a standard library task type and the principle of not paying for unused functionality.