Often I find my component having a public async (returning Task<T>
) method M
and DisposeCoreAsync
. Other components are using this component by calling M
. The application runs and at some point the shutdown is invoked. The shutdown is a cascade of disposals on all components. Assume execution of M
can take a long time. Then the problem arises that when DisposeCoreAsync
is called, one or more threads are executing M
and are somewhere in the middle of it. I'd like to find a primitive (or a simple-mechanism) P
, preferentially using pure .NET 5 libraries, such that I can write something like this:
public async Task<bool> M()
{
if (!P.Increment())
return false; // Shutdown detected
...
P.Decrement(); // Only after this happens for all active executions, disposal can finish
return result;
}
protected virtual async ValueTask DisposeCoreAsync()
{
...
P.Shutdown(); // This should prevent new P.Increment to succeed
await P.WaitAllFinishedAsync().ConfigureAwait(false);
// From here down we are guaranteed that no thread will ever execute the actual body of M().
...
}
So P.Increment
should increment a counter of active executions of M's body. P.Decrement
decrements the counter. P.Shutdown
makes sure that any further P.Increment
attempts fail. P.WaitAllFinishedAsync
waits until the counter of P goes to 0.
This all is trivial if I allow busy waiting. I can implement P.Increment
as Interlocked.Increment
, I can implement P.Decrement
as Interlocked.Decrement
, I can implement P.Shutdown
as Interlocked.Decrement
with some very high bit, such as 0x1000000. And P.Increment
succeeds only if after incrementing the new value is positive. Then I can implement P.WaitAllFinishedAsync
as while (P.counter > -0x1000000) Task.Delay(10);
But how to do it properly without WaitAllFinishedAsync
being a loop periodically checking if we are there yet? Moreover, how to do it with minimal overhead on execution of M
? It is only shutdown mechanism, so it should not put heavy burden on frequent executions of M
.
Of course, I'd probably use using
and make P.Decrement
inside of P.Dispose
instead of P.Increment/Decrement
, to make sure that any exceptions or returns from inside won't forget "freeing" P
, but that's just an implementation detail.
question from:
https://stackoverflow.com/questions/65850950/async-non-polling-disposal-mechanism-to-wait-until-all-method-calls-are-finished 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…