C# 什么';s c锁和x27;当锁定ConcurrentDictionary时,如何实现条件非阻塞锁?

C# 什么';s c锁和x27;当锁定ConcurrentDictionary时,如何实现条件非阻塞锁?,c#,multithreading,locking,C#,Multithreading,Locking,我的挑战: 避免在由多个用户针对多个ID从UI触发的方法DoWorkWithId中出现争用条件,例如lock 允许不同id重新进入DoWorkWithId,但不允许相同id重新进入,例如使用ConcurrentDictionary,其中id是键,值是对象 应该是非阻塞锁,以便线程跳过关键部分而不等待并让其他人知道它已经在运行(至少在第二次调用时正在运行),例如监视器.TryEnter或联锁。* 我的尝试: 我猜是1。二,。可以使用ConcurrentDictionary和lock private

我的挑战:

  • 避免在由多个用户针对多个ID从UI触发的方法
    DoWorkWithId
    中出现争用条件,例如
    lock
  • 允许不同id重新进入
    DoWorkWithId
    ,但不允许相同id重新进入,例如使用
    ConcurrentDictionary
    ,其中id是键,值是对象
  • 应该是非阻塞锁,以便线程跳过关键部分而不等待并让其他人知道它已经在运行(至少在第二次调用时正在运行),例如
    监视器.TryEnter
    联锁。*
  • 我的尝试:

    我猜是1。二,。可以使用
    ConcurrentDictionary
    lock

    private static readonly ConcurrentDictionary<Guid, object> concurrentDictionaryMethodLock = new();
    public string CallToDoWorkWithId(Guid id) // [Edited from DoWorkWithId]
    {
        concurrentDictionaryMethodLock.TryAdd(id, new object()); // atomic adding
        lock (concurrentDictionaryMethodLock[id])
        {
            DoWorkWithId(id);
        }
        return "done";
    }
    
    3.2

    为了正确地释放锁,我想我需要跟踪谁获得了锁,例如。
    私有静态只读ConcurrentDictionary bools=new()

    所以,我试着:

        private static readonly ConcurrentDictionary<Guid, object> concurrentDictionaryMethodLock = new();
        private static readonly ConcurrentDictionary<Guid, bool> bools = new();
        public string CallToDoWorkWithId(Guid id) // [Edited from DoWorkWithId]
        {
            concurrentDictionaryMethodLock.TryAdd(id, new object());
            bools.TryAdd(id, false);
            try
            {
                Monitor.TryEnter(concurrentDictionaryMethodLock[id], ref bools[id]); // error
                if (bools[id])
                {
                    DoWorkWithId(id);
                }
                else
                {
                    return "Process already running. Wait, refresh page and try again.";
                }
            }
            finally
            {
                if (bools[id])
                {
                    Monitor.Exit(concurrentDictionaryMethodLock[id]);
                }
            }
            return "done";
        }
    

    使用这种方法有任何缺陷吗?

    是否需要内部TryGetValue if语句?似乎你在这里混合了责任。该方法的其余部分应该只在同一时间成功地处理每个项目一次。谢谢,TryGetValue不是必需的,但会很好,这样我就可以向尝试运行该过程的其他用户显示更有意义的消息。这就是为什么我想访问这个值(用户名),而TryAdd不返回这个值,所以我想我需要它来访问这个值,同时可以删除这个值对。这看起来太复杂了。为什么不在主
    ConcurrentDictionary
    对象中存储一个布尔值,说明它是否正在使用。因此,例如,如果您的键/值对是一个guid/对象(如示例中所示),请向该对象添加一个属性,指示它是否正在使用。当有人访问该guid时,将其设置为true;当处理完成时,将其设置为false;当另一个用户尝试访问该对象时,它将显示该对象当前正在处理,并且您可以使用简单的
    if
    条件来拒绝访问。锁定
    ConcurrentDictionary
    似乎违反直觉。谢谢Ryan,这完全有道理。使用TryUpdate更改对象的bool类似于CompareExchange([MSDN][1]),因此它是原子的,使用起来安全。锁定ConcurrentDictionary是在我尝试执行条件锁定时出现的。[1] 你可以看看这个问题:。它包括一个带有
    键控监视器
    实现的答案,其中包含方法
    输入
    尝试输入
    退出
    var lockObj = new Object();
    bool lockTaken = false;
    
    try
    {
        Monitor.TryEnter(lockObj, ref lockTaken);
        if (lockTaken)
        {
            // The critical section.
        }
        else
        {
            // The lock was not acquired.
        }
    }
    finally
    {
        // Ensure that the lock is released.
        if (lockTaken)
        {
            Monitor.Exit(lockObj);
        }
    }
    
        private static readonly ConcurrentDictionary<Guid, object> concurrentDictionaryMethodLock = new();
        private static readonly ConcurrentDictionary<Guid, bool> bools = new();
        public string CallToDoWorkWithId(Guid id) // [Edited from DoWorkWithId]
        {
            concurrentDictionaryMethodLock.TryAdd(id, new object());
            bools.TryAdd(id, false);
            try
            {
                Monitor.TryEnter(concurrentDictionaryMethodLock[id], ref bools[id]); // error
                if (bools[id])
                {
                    DoWorkWithId(id);
                }
                else
                {
                    return "Process already running. Wait, refresh page and try again.";
                }
            }
            finally
            {
                if (bools[id])
                {
                    Monitor.Exit(concurrentDictionaryMethodLock[id]);
                }
            }
            return "done";
        }
    
    private static readonly ConcurrentDictionary<Guid, string> concurrentDictionaryMethodLock = new();
    public string CallToDoWorkWithId(Guid id) // [Edited from DoWorkWithId]
    {
        var userName = GetUserName();
        if (!concurrentDictionaryMethodLock.TryAdd(id, userName)) // false means unable to add, was already added, please skip and let UI know
        {
            if (concurrentDictionaryMethodLock.TryGetValue(id, out var value))
            {
                return $"Please wait. {value} started process already.";
            }
            else // in case id has been removed in the meantime, even TryGetOrAdd is not atomic (MSDN remarks about valueFactory) 
            {
                return "Process was running and finished by now. Please refresh and try again.";
            }
        }
        try
        {
            DoWorkWithId(id);
        }
        finally
        {
            concurrentDictionaryMethodLock.TryRemove(id, out _);
        }
        return "done";
    }