Using thread pool cleanup groups to clean up many things at once

Today's Little Program demonstrates thread pool cleanup groups. When you associate a thread pool item with a cleanup group, you can perform bulk operations on the group. That can save you a lot of bookkeeping.

Remember that Little Programs do little to no error checking.

#include <windows.h>
#include <stdio.h> // horrors! Mixing stdio and C++!

    PVOID                 /* Parameter */,
    PTP_TIMER             /* Timer */
    // Say what time the callback ran.
    printf("%p at %d\n", Instance, GetTickCount());

main(int, char**)
    // Create an environment that we use for our timers.

    // Create a thread pool cleanup group and associate it
    // with the environment.
    auto cleanupGroup = CreateThreadpoolCleanupGroup();

    // Say what time we started
    printf("Start: %d\n", GetTickCount());

    // Ask for a one-second delay
    LARGE_INTEGER dueTime;
    dueTime.QuadPart = -10000LL * 1000; // one second
    FILETIME ftDue = { dueTime.LowPart, dueTime.HighPart };

    // Create ten timers to run after one second.
    for (int i = 0; i < 10; i++) {
        auto timer = CreateThreadpoolTimer(Callback,
        SetThreadpoolTimer(timer, &ftDue, 0, 500);

    // Wait a while - the timers will run.

    // Clean up the group.

    // Close the group.

There is some trickiness in building the FILETIME structure to specify that we want to run after a one-second delay. First, the value is negative to indicate a relative timeout. Second, we cannot treat the FILETIME as an __int64, so we use a LARGE_INTEGER as an intermediary.

When we create the ten timers, we associate them with the environment, which is in turn associated with the cleanup group. This puts all the timers into the cleanup group, which is a good thing, because we didn't save the timer handles!

When it's time to clean up the timers, we use Close­Thread­pool­Cleanup­Group­Members, which does the work of closing each individual timer in the cleanup group. This saves us the trouble of having to remember all the timers ourselves and manually closing each one.

For our next trick, comment out the Sleep(1500); and run the program again. This time, the timers don't run at all. That's because we closed them before they reached their due time. We let the cleanup group do the bookkeeping for us.

Comments (10)
  1. Myria says:

    A secondary reason you can't reinterpret_cast FILETIME to __int64 *: endianness.  Unlike LARGE_INTEGER, FILETIME does not swap its two fields' positions on e.g. Xbox 360.

    I much prefer stdio to the C++ ways of doing standard input and output, so I'm with you on that.  That said, a second "horrors" comment ought to be on using main instead of wmain in a Windows program =^_^=

  2. Cesar says:

    The horror is not mixing stdio and C++. The horror is mixing stdio and C++ the old way. You should use cstdio instead of stdio.h.


  3. skSdnW says:

    The horror is mixing a compiler specific attribute like __cdecl in otherwise "portable" code when you could use STDMETHODVCALLTYPE (or perhaps even add a … parameter?) :)

    [I assume you're talking about main? STDMETHODVCALLTYPE is wrong here, because main must be __cdecl and should not follow the variadic COM method call convention. You are relying on implementation details. -Raymond]
  4. Joshua says:

    Expected: int main(

    Got: int __cdecl main(

    The code is portable between compilers except for __cdecl is compiler specific and not necessary anyway.

    [__cdecl is required if you compile with /Gz, for example, which is common because __stdcall is a more compact calling convention than __cdecl. I don't understand the portability concern here. You're using windows.h. That's not portable. -Raymond]
  5. Joshua says:

    [__cdecl is required if you compile …]

    Any reason for not using CDECEL then?

    [You're using windows.h. That's not portable. -Raymond]

    That's what you think. Change __cdecl to CDECL and it builds w/ gcc.

  6. Brian_EE says:

    [You're using windows.h. That's not portable. -Raymond]

    There's a difference between target platform portability and compiler vendor portability.

  7. text rendering says:

    I want to comment on your ClearType article, but unfortunately it's already locked.

    Rendering ClearType text is not only dependent on semitransparent backgrounds, it's also dependent on other semitransparent windows that are rendered in front of the text, uncommon but can happen. Thus to correctly render ClearType text you have to know in advance if other semitransparent bitmaps will be overlayed over the text your rendering. ClearType text overlayed over ClearType text is an example of this, which Windows doesn't render correctly, the ClearType engine isn't competent enough to do this correctly.

  8. Myria says:

    @Raymond: Though it's true that I often write extern "C" int __cdecl wmain myself, it is the case that __cdecl is unnecessary in Visual C++ even with the /Gz compiler option.  Visual C++ special-cases the main and wmain functions.

    // compile with /Ox /Oy /Gz /Fa

    int testfunc(int, wchar_t **) { return 66; }

    int wmain(int, wchar_t **) { return 66; }

    _wmain PROC

    mov eax, 66

    ret 0

    _wmain ENDP

    ?testfunc@@YGHHPAPA_W@Z PROC

    mov eax, 66

    ret 8

    ?testfunc@@YGHHPAPA_W@Z ENDP

    wmain is not just automatically __cdecl, it's automatically extern "C", which is rather logical.

  9. HARBMOBARKI says:


  10. Alex Cohn says:

    Too bad `auto` is not part of C circa 2015. This little shorthand is the only (but very good) reason to use C++ compiler here,

Comments are closed.

Skip to main content