If you don't need a Wait() call to return Task , but content with await Wait() , you can implement custom awaiter / awaitable.
See this link for an overview of the wait pattern used by the compiler.
When implementing custom expectations, you will simply be dealing with delegates, and the actual โwaitingโ is yours. When you want to โwaitโ for a condition, you can often keep a list of pending continuations, and whenever the condition is met, you can call these continuations. You just need to deal with synchronization, based on the fact that await can be called from arbitrary threads. If you know that you only ever await from one thread (say, a UI thread), then you don't need synchronization at all!
I will try to give you the ability to block, but does not guarantee that this is correct. If you do not understand why all the conditions of the race are safe, you should not use it and implement the async / await protocol using lock statements or other methods that you know how to debug.
public sealed class AsyncMonitor { private PulseAwaitable _currentWaiter; public AsyncMonitor() { _currentWaiter = new PulseAwaitable(); } public void Pulse() {
Usually, you donโt worry about which thread executions continue in, because if they are asynchronous methods, the compiler has already inserted the code (to continue) in order to switch back to the desired thread, you do not need to do this manually in each expected implementation.
[edit]
As a starting point for how you can implement a lock implementation, I will describe it using the lock statement. It should be easily replaced with a spin lock or some other locking technique. Using the structure as expected, it even has the advantage that it does not make additional selection except the original object. (Of course, in the compiler layout on the call side there are allocations in the async / await structure, but you cannot get rid of them.)
Please note that the iteration counter will only increase for each Wait + Pulse pair and will eventually go negative, but this is normal. We just need to reduce the time it takes to continue beeing until it can call GetResult. 4 billion pairs of waiting + pulses should be enough time for any waiting continuations to call the GetResult method. If you do not want this risk, you can use long or Guid for a more unique iteration counter, but IMHO int is good for almost all scenarios.
public sealed class AsyncMonitor { public struct Awaitable : INotifyCompletion {