在 .NET 5 C# 中等待所有方法调用完成的异步非轮询处理机制

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

我经常发现我的组件有一个 public 异步(返回 Task<T>)方法 MDisposeCoreAsync。其他组件正在通过调用 M 使用此组件。应用程序运行并在某个时候调用关闭。关闭是对所有组件的一系列处置。假设 M 的执行可能需要很长时间。那么问题就来了,当DisposeCoreAsync被调用的时候,有一个或者多个线程正在执行M并且在中间的某个地方。我想找到一个原始的(或简单的机制)P,优先使用纯 .NET 5 库,这样我就可以写这样的东西:

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().
    ...
}

所以 P.Increment 应该增加 M 主体的活跃执行计数器。 P.Decrement 递减计数器。 P.Shutdown 确保任何进一步的 P.Increment 尝试都会失败。 P.WaitAllFinishedAsync 等到 P 的计数器变为 0。

如果让我忙着等待,这一切都是微不足道的。我可以将 P.Increment 实现为 Interlocked.Increment,我可以将 P.Decrement 实现为 Interlocked.Decrement,我可以将 P.Shutdown 实现为 Interlocked.Decrement,并且具有非常高的位,例如 0x1000000。并且 P.Increment 只有在递增新值后为正时才会成功。然后我可以将 P.WaitAllFinishedAsync 实现为 while (P.counter > -0x1000000) Task.Delay(10);

但是如何正确地做到这一点而不 WaitAllFinishedAsync 是一个周期性地检查我们是否已经到达的循环?此外,如何以最小的开销执行 M?它只是关闭机制,因此不应该对 M.

的频繁执行造成沉重负担

当然,我可能会使用 using 并在 P.Dispose 中创建 P.Decrement 而不是 P.Increment/Decrement,以确保任何异常或 returns 从内部不会忘记“释放”P,但这只是一个实现细节。

您正在寻找的原语是异步兼容的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,您可以从中获取灵感。