February 21st, 2022

COM asynchronous interfaces, part 6: Learning about completion without polling

So far, the way we’ve been waiting for an asynchronous call to complete is by polling for it. Is there a way to be notified directly when the operation completes? For example, while doing work, we may call out to other methods, and if the asynchronous call completes while those other methods are busy, we’d like to cancel those other calls so we can get back to the main work of dealing with the asynchronous call.

One way to do this is to peek at the kernel event handle that is hiding inside the ISynchronize interface. The ISynchronize­Handle interface lets you do that.

  HANDLE rawEventHandle = nullptr;
  call.as<::ISynchronizeHandle>()->GetHandle(&rawEventHandle);

Note that the handle that comes back is still owned by the call, so don’t close it. This is not usually a problem because you typically keep the call around since you will want to get the result of the asynchronous call, For example, you might want to suspend the current coroutine until the asynchronous call has completed.

  co_await winrt::resume_on_signal(rawEventHandle);
  // coroutine resumes when the asynchronous call completes

  call.Finish_Something();

Or you might initiate multiple asynchronous calls, and you want to process the results from whichever call finishes first.

  HANDLE readyEvents[2];

  call1.as<::ISynchronizeHandle>()->GetHandle(&readyEvents[0]);
  call2.as<::ISynchronizeHandle>()->GetHandle(&readyEvents[1]);

  DWORD index;
  auto hr = CoWaitForMultipleHandles(COWAIT_DEFAULT, INFINITE,
                                     2, readyEvents, &index);

  if (hr == S_OK) {
    if (index == 0) { /* deal with call1 */ }
    if (index == 1) { /* deal with call2 */ }
  }

If you want to use the handle in a way unrelated to the call it came from, then duplicate the handle, at which point you become responsible for the lifetime of the duplicate.

  winrt:handle eventHandle;
  winrt::check_bool(DuplicateHandle(
    GetCurrentProcess(), rawEventHandle,
    GetCurrentProcess(), eventHandle.put(),
    SYNCHRONIZE, FALSE, 0));

The duplicate handle eventHandle is your responsibility to close. You can hand it to some other component which uses it to know when the asynchronous method call has completed. Just remember to close it when you’re done.

Next time, we’ll look at a way of getting called back directly when the asynchronous call completes.

Topics
Code

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.

4 comments

Discussion is closed. Login to edit/delete existing comments.

  • Александр Давыдкин · Edited

    Back in 2014 you’ve written excellent explanation about default base address (0x00400000) in EXE PE for 32 bit: https://devblogs.microsoft.com/oldnewthing/20141003-00/?p=43923
    However I wonder why in 64 bit linker this address is 0x140000000 – notedly bigger than 4Gb which could (possibly) explain some backward compatibility for 32-bit virtual machines (or what?).
    Thanks a lot for this blog!

    • Scarlet Manuka

      It seems pretty reasonable that the default address for a 64-bit image would be above the 4GB boundary.
      No 64-bit anything is going to be running in a 32-bit VM so I’m not sure what you’re getting at with the backwards compatibility comment.

    • MGetz · Edited

      1. The default on x64 architectures should be relocatable in all cases

      2. Assuming that a DLL or EXE is marked otherwise choosing that address would deliberately break assumptions about loading thus allowing developers to fix their code to be relocatable.

      3. It allows tools to detect bad practices as a x64 binary shouldn't have a specified base address for security reasons.

      4. The address is immaterial to 32bit code because that's a different entry in the GDT...

      Read more
      • Александр Давыдкин · Edited

        "(( I expect Raymond to delete both of these comments as they are irrelevant to the post above))"

        I'm sorry if I break rules, but I don't know how to ask this question about very old entry in blog.

        Documentation ( here: https://docs.microsoft.com/en-us/cpp/build/reference/base-base-address?view=msvc-170 ) says MS linker in 64 bit will set this address to 0x140000000.
        After I asked question here I've got interesting answer in some programming forum: default address above 4Gb in 64bit could...

        Read more