我需要一段代码,根据参数键,它只允许一个线程同时执行:

    private static readonly ConcurrentDictionary<string, SemaphoreSlim> Semaphores = new();

    private async Task<TModel> GetValueWithBlockAsync<TModel>(string valueKey, Func<Task<TModel>> valueAction)
    {
        var semaphore = Semaphores.GetOrAdd(valueKey, s => new SemaphoreSlim(1, 1));

        try
        {
            await semaphore.WaitAsync();

            return await valueAction();
        }
        finally
        {
            semaphore.Release(); // Exception here - System.ObjectDisposedException
            if (semaphore.CurrentCount > 0 && Semaphores.TryRemove(valueKey, out semaphore))
            {
                semaphore?.Dispose();
            }
        }
    }

我时常会遇到这样的错误:

The semaphore has been disposed. : System.ObjectDisposedException: The semaphore has been disposed.
   at System.Threading.SemaphoreSlim.CheckDispose()
   at System.Threading.SemaphoreSlim.Release(Int32 releaseCount)
   at Project.GetValueWithBlockAsync[TModel](String valueKey, Func`1 valueAction)

我能想象的所有情况都是线程安全.请帮忙,我错过了什么案子?

推荐答案

正如 comments 中所讨论的,这里有几个比赛条件.

  1. 线程1持有锁,线程2等待WaitAsync().线程1释放锁,然后判断semaphore.CurrentCount,然后线程2才能获取锁.
  2. 线程1持有锁,释放锁,并判断通过的semaphore.CurrentCount个.线程2进入GetValueWithBlockAsync,调用Semaphores.GetOrAdd并获取信号量.线程1然后调用Semaphores.TryRemove并使信号量双工.

你真的需要锁定从Semaphores中删除一个条目的决定——这是不可能的.您也无法跟踪是否有线程从Semaphores获取了信号量(或者正在等待,或者尚未到达该点).

一种方法是这样做:在每个人之间共享一个锁,但只有在获取/创建信号量并决定是否处理它时才需要它.我们手动跟踪当前有多少线程对特定信号量感兴趣.当一个线程释放了信号量后,它会获取共享锁,以判断当前是否有其他人对该信号量感兴趣,并且只有在没有其他人感兴趣时才会处理该信号量.

private static readonly object semaphoresLock = new();
private static readonly Dictionary<string, State> semaphores = new();

private async Task<TModel> GetValueWithBlockAsync<TModel>(string valueKey, Func<Task<TModel>> valueAction)
{
    State state;
    lock (semaphoresLock)
    {
        if (!semaphores.TryGetValue(valueKey, out state))
        {
            state = new();
            semaphores[valueKey] = state;
        }
        
        state.Count++;
    }

    try
    {
        await state.Semaphore.WaitAsync();

        return await valueAction();
    }
    finally
    {
        state.Semaphore.Release();
        lock (semaphoresLock)
        {
            state.Count--;
            if (state.Count == 0)
            {
                semaphores.Remove(valueKey);
                state.Semaphore.Dispose();
            }
        }
    }
}

private class State
{
    public int Count { get; set; }
    public SemaphoreSlim Semaphore { get; } = new(1, 1);
}

当然,另一个 Select 是让Semaphores人成长.也许你有一个周期性的操作来判断并清除任何没有被使用的东西,但是这当然需要得到保护,以确保线程不会突然对正在被清除的信号量感兴趣.

.net相关问答推荐

为什么在WinForm应用程序中创建组件类椭圆会在www.example.com中没有响应

";Make Async ValueTask/ValueTask方法分期分配发生了什么?

使用托管身份而不是检测密钥配置Application Insights

无法在 Blazor Server 应用程序中触发 InputRadio 的 onchange 事件

C#.Net 中的可选返回

什么是提升运算符?

将 BitmapImage 转换为 Bitmap,反之亦然

.NET 应用程序的链接器状态(又名请先生,我可以有一个链接器2009 年版)

重新启动(回收)应用程序池

数据库架构更改后更新 LINQ to SQL 类的最佳方法

C# 属性实际上是方法吗?

在 C# 中将字符串转换为十六进制字符串

WCF反序列化如何在不调用构造函数的情况下实例化对象?

如何比较 C# 中的(目录)路径?

如何从 .NET 中的流中获取 MemoryStream?

强制 XmlSerializer 将 DateTime 序列化为 'YYYY-MM-DD hh:mm:ss'

无法加载文件或程序集Antlr3.Runtime (1)或其依赖项之一

C# (.NET) 设计缺陷

如何隐藏 WPF ListView 的标题?

在foreach循环中修改列表的最佳方法是什么?