ICorDebug maintains a stop-count, and so if you call ICorDebugProcess::Stop() twice in a row, the 1st stop does the real asynchronous-break, and the 2nd stop is basically a nop that just increments a counter. You’ll then need to call ICorDebugProcess::Continue() twice. The first Continue() call just decrements the stop-counter; and then the 2nd call does the real continue.
1) A debugger implements Async-break by calling ICDProcess::Stop(). That API is synchronous and will block until the debuggee stops. In fact, that is one of the few synchronous invasive methods in all of ICorDebug. Once it successfully returns, the debuggee is synchronized and you need to call an extra Continue() to resume it.
2) Dispatching a callback bumps up the stop-counter, and so it takes 1 call to Continue() to resume from a managed event callback. So if you call stop inside a callback, then you’ll need to call an extra Continue.
3) You can also call both Stop + Continue on any threads.
Why have a stop-count?
You could call Stop() on thread 1 which could race with ICD’s thread dispatching a managed debug event. This scenario is a motivation for the stop-count. If we didn’t have the stop-count, then Continuing the Stop() on thread 1 may accidentally continue the debug event too.