.net 仅锁定ID

2g32fytz  于 2023-01-27  发布在  .NET
关注(0)|答案(5)|浏览(165)

我有一个方法需要独占地运行一段代码,但是我只想在真正需要的时候添加这个限制。根据Id值(Int 32),我将加载/修改不同的对象,所以锁定所有线程的访问是没有意义的。下面是第一次尝试这样做-

private static readonly ConcurrentDictionary<int, Object> LockObjects = new ConcurrentDictionary<int, Object>();
void Method(int Id)
{
    lock(LockObjects.GetOrAdd(Id,new Object())
    {
       //Do the long running task here - db fetches, changes etc
       Object Ref;
       LockObjects.TryRemove(Id,out Ref);
    }

}

我怀疑这是否可行-TryRemove可能会失败(这将导致ConcurrentDictionary不断变大)。
一个更明显的错误是,TryRemove成功地删除了对象,但如果有其他线程(相同ID)正在等待(锁定)此对象,然后一个具有相同ID的新线程进入并添加一个新对象并开始处理,因为没有其他线程在等待它刚刚添加的对象。
我应该使用TPL或者某种ConcurrentQueue来排队我的任务吗?最简单的解决方案是什么?

e37o9pze

e37o9pze1#

我使用类似的方法来锁定相关项的资源,而不是一揽子资源锁定...它工作得很完美。
差不多了,但是你真的不需要从字典中删除这个对象;只让具有该ID的下一个对象获得该对象上的锁。
你的应用程序中唯一标识的数量肯定是有限制的吧?这个限制是什么?

blpfk2vs

blpfk2vs2#

我看到的主要语义问题是,一个对象可以在没有被列在集合中的情况下被锁定,因为锁中的最后一行删除了它,等待线程可以拾取它并锁定它。
将集合更改为应保护锁的对象集合。不要不要将其命名为LockedObjects,并且不要从集合中移除对象,除非您认为不再需要该对象。
我总是把这种类型的对象看作是一把钥匙,而不是一把锁或一个被阻塞的对象;对象未被锁定,它是锁定代码序列的密钥。

n53p2ov0

n53p2ov03#

我使用了下面的方法。不检查原始ID,而是获取int类型的小散列代码来获取现有对象以进行锁。锁的计数取决于您的情况-锁计数器越多,冲突的概率越小。

class ThreadLocker
{
    const int DEFAULT_LOCKERS_COUNTER = 997;
    int lockersCount;
    object[] lockers;

    public ThreadLocker(int MaxLockersCount)
    {
        if (MaxLockersCount < 1) throw new ArgumentOutOfRangeException("MaxLockersCount", MaxLockersCount, "Counter cannot be less, that 1");
        lockersCount = MaxLockersCount;
        lockers = Enumerable.Range(0, lockersCount).Select(_ => new object()).ToArray();
    }
    public ThreadLocker() : this(DEFAULT_LOCKERS_COUNTER) { }

    public object GetLocker(int ObjectID)
    {
        var idx = (ObjectID % lockersCount + lockersCount) % lockersCount;
        return lockers[idx];
    }
    public object GetLocker(string ObjectID)
    {
        var hash = ObjectID.GetHashCode();
        return GetLocker(hash);
    }
    public object GetLocker(Guid ObjectID)
    {
        var hash = ObjectID.GetHashCode();
        return GetLocker(hash);
    }
}

用法:

partial class Program
{
    static ThreadLocker locker = new ThreadLocker();
    static void Main(string[] args)
    {
        var id = 10;
        lock(locker.GetLocker(id))
        {

        }
    }
}

当然,您可以使用任何散列代码函数来获取对应的数组索引。

wswtfjt7

wswtfjt74#

如果你想使用ID本身,并且不允许由哈希代码引起的冲突,你可以使用下一种方法:维护对象的字典,并存储关于想要使用ID的线程数量的信息:

class ThreadLockerByID<T>
{
    Dictionary<T, lockerObject<T>> lockers = new Dictionary<T, lockerObject<T>>();

    public IDisposable AcquireLock(T ID)
    {
        lockerObject<T> locker;
        lock (lockers)
        {
            if (lockers.ContainsKey(ID))
            {
                locker = lockers[ID];
            }
            else
            {
                locker = new lockerObject<T>(this, ID);
                lockers.Add(ID, locker);
            }
            locker.counter++;
        }
        Monitor.Enter(locker);
        return locker;
    }
    protected void ReleaseLock(T ID)
    {
        lock (lockers)
        {
            if (!lockers.ContainsKey(ID))
                return;

            var locker = lockers[ID];

            locker.counter--;

            if (Monitor.IsEntered(locker))
                Monitor.Exit(locker);

            if (locker.counter == 0)
                lockers.Remove(locker.id);
        }
    }

    class lockerObject<T> : IDisposable
    {
        readonly ThreadLockerByID<T> parent;
        internal readonly T id;
        internal int counter = 0;
        public lockerObject(ThreadLockerByID<T> Parent, T ID)
        {
            parent = Parent;
            id = ID;
        }
        public void Dispose()
        {
            parent.ReleaseLock(id);
        }
    }
}

用法:

partial class Program
{
    static ThreadLockerByID<int> locker = new ThreadLockerByID<int>();
    static void Main(string[] args)
    {
        var id = 10;
        using(locker.AcquireLock(id))
        {

        }
    }
}
kgsdhlau

kgsdhlau5#

有一些迷你库可以帮你做到这一点,比如AsyncKeyedLock,我已经用过了,它省去了我很多麻烦。

相关问题