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

    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相关问答推荐

在 .NET 7 项目上设置 Sentry 时遇到问题

从删除项目时重新索引的列表中删除项目的最佳算法是什么?

.Net MAUI Android 无法与 API localhost 对话

什么是提升运算符?

什么是表达式树,如何使用它们,为什么要使用它们?

.NET 的future 版本会支持 C# 中的元组吗?

为什么 .Contains 慢?通过主键获取多个实体的最有效方法?

ASP.NET MVC:隐藏字段值不会使用 HtmlHelper.Hidden 呈现

如何使用c#从excel文件中读取数据

.net 服务总线建议?

参数命名:文件名还是文件名?

在 .NET 中填充整数列表

非通用 TaskCompletionSource 或替代

如何将 XPath 与 XElement 或 LINQ 一起使用?

Moq - 不可覆盖的成员不能用于设置/验证表达式

Dispatcher.CurrentDispatcher 与 Application.Current.Dispatcher

log4net的正确使用方法(记录器命名)

带有嵌套控件的设计模式

根据条件从列表中删除项目

您可以将 Microsoft Entity Framework 与 Oracle 一起使用吗?