Parallel Programming with C++ – Part 3 – Queuing Asynchronous Procedure Calls

In part 1 of the Parallel Programming with C++ series I introduced asynchronous procedure calls (APCs) and how they can be used with alertable I/O to process asynchronous I/O requests without blocking an application’s thread. In part 2, I showed how APC handling can be integrated with window message loop.

Although that covers the most common use cases for APCs there are still many more ways in which you can use APCs. Windows uses APCs extensively and many subsystems use and expose them in various ways. I will be covering some of these in future articles in this series as it relates to other concurrency topics. For now I want to wrap up the discussion of APCs by showing how you can queue your own user-mode APCs. In a future column I’ll show you how you can set APCs to be queued in the future which can be more useful but for now let’s take a look at the QueueUserAPC function:
 
if (!::QueueUserAPC(apcFunction,
                    threadHandle,
                    data))
{
    // Call GetLastError for more information.
}

The first parameter is the APC function to be queued. The second is the handle to thread identifying the APC queue. The last parameter is there for you to pass any contextual information to the APC function. Let’s take a look at a more complete example. Here’s a simple thread procedure that queues APCs:

bool m_stopped = false;

void CALLBACK OnEvent(ULONG_PTR context)
{
    std::cout << context << std::endl;
}

void CALLBACK OnStopped(ULONG_PTR context)
{
    ASSERT(0 == context);

    std::cout << "Stopped" << std::endl;

    m_stopped = true;
}

DWORD WINAPI Producer(HANDLE consumerThread)
{
    for (int index = 0; index < 10; ++index)
    {
        ::Sleep(1000);

        VERIFY(::QueueUserAPC(OnEvent,
                              consumerThread,
                              index));
    }

    ::Sleep(1000);

    VERIFY(::QueueUserAPC(OnStopped,
                          consumerThread,
                          0));

    return 0;
}

The OnEvent APC function is queued 10 times in 1 second intervals and then the OnStopped APC function is queued a second later before the thread terminates. The thread parameter is actually a handle to the thread that the APCs should be queued on. The OnEvent APC function simply prints the context value. The OnStopped APC function sets the m_stopped value to true to signal the end of the operation.

An application can then make use of this producer thread procedure by creating the thread and then waiting for the APC functions to be queued. The only real trick at this point is figuring out how to get the producer thread the handle to the current thread. The GetCurrentThread function returns a handle to the current thread but it’s really a pseudo handle representing whatever happens to be the current thread. So to pass this handle to another thread you need to use the DuplicateHandle function to create an actual handle that is portable. And here is the result:

int main()
{
    CHandle consumerThread;

    if (!::DuplicateHandle(::GetCurrentProcess(),
                           ::GetCurrentThread(),
                           ::GetCurrentProcess(),
                           &consumerThread.m_h,
                           THREAD_SET_CONTEXT, // only permission required by QueueUserAPC
                           FALSE, // not inheritable
                           0)) // no options
    {
        // Call GetLastError for more information.
    }

    ASSERT(0 != consumerThread);

    CHandle producerThread(::CreateThread(0, // default security
                                          0, // default stack size
                                          Producer, // thread proc
                                          consumerThread, // context
                                          0, // flags
                                          0)); // ignore thread id

    if (0 == producerThread)
    {
        // Call GetLastError for more information.
    }

    while (!m_stopped)
    {
        ::SleepEx(INFINITE, TRUE);
    }
}

Finally I should point out that you need to be conscious of what system calls you make within your APC functions. Specifically you need to avoid calling other functions that might directly or indirectly enter an alertable state. This may not always be obvious so be sure to read the documentation carefully!

That’s it’s for today.

Read part 4 now: I/O Completion Ports

© 2007 Kenny Kerr

2 Comments

  • Excellent series Kenny! I look forward to part 4 and beyond. It's amazing that I've done Win32 dev since 1991 and have not used APC. It's going into the toolbox and this is an excellent series to help me get started.

    Do you think user APCs would be usable with ReadDirectoryChangesW (maybe with IO Completion Ports) to asynchronously trigger an APC when a directory/file changes? I fire up a worker thread for this today and it comes to mind as a possible use of APC's... What do other people typically use them for?

    John

  • John: thanks for the comment!

    ReadDirectoryChangesW is pretty versatile. It supports synchronous as well as asynchronous completion. For async you have the option of using the vanilla GetOverlappedResult approach, completion ports, or APCs. For a client application I’d use APCs (as long as you’re not processing too many changes) and for a service I’d use completion ports.

    I’ll probably talk about completion ports in part 4...

Comments have been disabled for this content.