无法在锁中等待,如何确保不从多个线程访问异步变量和方法?

Unable to await in a lock, how do I ensure async variable and method are not accessed from multiple threads?

我有以下代码:

public const int ThreadLimitMax = 128;
private static object setThreadLimitLock = new object();
private static SemaphoreSlim totalThreadLimiter = new SemaphoreSlim(ThreadLimit, ThreadLimitMax);
        
public static int ThreadLimit { get; private set; } = 128;

public static async Task SetThreadLimit(int max)
{
    if (max > ThreadLimitMax)
        throw new ArgumentOutOfRangeException(nameof(max), $"Cannot have more than {ThreadLimitMax} threads.");
    if (max < 1)
        throw new ArgumentOutOfRangeException(nameof(max), $"Cannot have less than 1 threads.");

    lock (setThreadLimitLock)
    {
        int difference = Math.Abs(ThreadLimit - max);
        if (max < ThreadLimit)
        {
            for (int i = 0; i < difference; i++)
            {
                await totalThreadLimiter.WaitAsync().ConfigureAwait(false);
            }
        }
        else if (max > ThreadLimit)
        {
            totalThreadLimiter.Release(difference);
        }
        ThreadLimit = max;
    }
}

我正在尝试创建一个方法来修改 totalThreadLimiter 中可用线程的数量。我将最大线程数保留在 ThreadMaxLimit 整数中。

要更改线程数,我需要确保在更改最大线程数操作完成之前不会访问 ThreadLimit。我还需要确保该方法被阻止,直到 totalThreadLimiter 完成所有 WaitAsync() 调用。

我该怎么做?

lock 是 API 围绕 Monitor 的助手,这是一个线程绑定同步原语,这意味着它不适合与 await 一起使用,因为无法保证当您从未完成的异步操作返回时您将在哪个线程上。

最终,您需要一个异步感知同步原语;最容易获得的是 SemaphoreSlim,它有你用来获取锁的 WaitAsync() API,还有一个 try/finally 调用 Release().

在问题的代码中,根据代码分支,您要么获取(仅)信号量,要么释放信号量;这几乎肯定是错误的。正确的用法更像是:

await totalThreadLimiter.WaitAsync();
try
{
    // some code with "await" here
}
finally
{
    totalThreadLimiter.Release();
}