0XC0020062

RPC async handle 0XC0020062: fix by closing pipes in order

Server & Cloud Intermediate 👁 7 views 📅 May 27, 2026

This error fires when an RPC call tries to use an async handle that's already been invalidated. The root cause is almost always closing the pipe handle before the call completes.

When this error shows up

You're writing a Windows service or a high-performance IPC component using asynchronous RPC. Maybe you're using RpcAsyncInitializeHandle, binding to a named pipe endpoint, and kicking off calls with RpcAsyncSendReceive. Then, mid-test or under load, you get status 0xC0020062RPC_NT_INVALID_ASYNC_HANDLE. The call returns this error even though you know you initialized the handle. It's not a code path you expected to fail. The timing is suspicious: it tends to happen when you're shutting down connections or cleaning up resources after a failed call.

Root cause

What's actually happening here is that the RPC runtime holds a reference to the async handle while the call is in flight. When you close the underlying pipe handle (or the binding handle) before the async call completes, the RPC runtime detects that the handle's context has been destroyed. It then marks the async handle as invalid. The next time you try to use it — even just to check status — you get 0xC0020062.

The reason pipe handle ordering matters is that RpcBindingFree or CloseHandle on the pipe doesn't wait for outstanding async calls. The async handle's internal state machine transitions to RPC_S_ASYNC_CALL_INVALID the moment its underlying transport gets torn down. This isn't a bug in your code per se — it's a design constraint of the async RPC model. You must guarantee that no call is pending when you release any resource the call depends on.

Fix: close handles in the right order

The fix is mechanical but easy to get wrong. The ordering is critical. Here's the sequence that works on Windows Server 2016 through 2022, and on Windows 10/11.

  1. Cancel the async call first
    Call RpcAsyncCancelCall with the async handle. This tells the RPC runtime to abandon the call. It returns immediately — does not wait for completion.
  2. Wait for the call to complete
    Call RpcAsyncGetCallStatus in a loop with a short sleep (say 50ms) until the status is no longer RPC_S_ASYNC_CALL_PENDING (0x000003E9). The call will now be in a terminal state — cancelled, failed, or completed. Don't skip this step. If you do, the runtime still thinks the call is active.
  3. Close the async handle
    Call RpcAsyncDestroyHandle. This releases the internal resources for that async call. After this, the handle value is invalid.
  4. Free the binding handle
    Call RpcBindingFree on the binding handle. At this point, no async calls reference it anymore, so it's safe.
  5. Close the pipe handle
    If you opened the named pipe manually (e.g., with CreateFile), call CloseHandle now. This must be last.

Here's a code snippet illustrating the pattern:

RPC_ASYNC_STATE async;
RpcAsyncInitializeHandle(&async, sizeof(async));
// ... bind and call RpcAsyncSendReceive ...

// On shutdown or error:
RpcAsyncCancelCall(&async, TRUE);
while (RpcAsyncGetCallStatus(&async) == RPC_S_ASYNC_CALL_PENDING) {
    Sleep(50);
}
RpcAsyncDestroyHandle(&async);
RpcBindingFree(&hBinding);
CloseHandle(hPipe);

A key detail: the second param to RpcAsyncCancelCall should be TRUE for abortive cancel. If you pass FALSE, the runtime waits for a graceful abort, which may also fail if the pipe is already broken. Stick with TRUE.

What to check if it still fails

  • Are you calling RpcAsyncDestroyHandle twice? That's undefined behavior and can give the same error. Track ownership of the handle — it should be destroyed exactly once.
  • Is the call truly pending? Sometimes RpcAsyncGetCallStatus returns RPC_S_OK even after cancel. That's fine — the call completed before the cancel took effect. You can skip the wait loop in that case, but still call RpcAsyncDestroyHandle.
  • Are you using multiple threads? If one thread closes the handle while another thread is still in RpcAsyncSendReceive, you'll hit this error. Serialize access to each async handle.
  • Did the server end the call early? If the server calls RpcAsyncCompleteCall on its side and then closes its own binding, the client's async handle may become invalid before the client finishes processing. Use a completion callback on the client side to handle this race.

If none of those apply, check the Event Viewer under Applications and Services Logs > Microsoft > Windows > RPC. The RPC runtime sometimes logs more detail about which handle got invalidated and why. That's your last resort — and sometimes the only clue in complex multi-threaded scenarios.

Was this solution helpful?