Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
339 views
in Technique[技术] by (71.8m points)

Async non-polling disposal mechanism to wait until all method calls are finished in .NET 5 C#

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The primitive you're looking for is an async-compatible countdown event. There is no type in the BCL that does this, but you can build one yourself. I have an open-source one here you can use for inspiration.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...