对ConcurrentDictionary中的项目进行C#线程安全的原子操作

时间:2019-12-16 03:59:53

标签: c# concurrency thread-safety atomicity

我如何对键可能尚未在ConcurrentDictionary中的项目执行原子操作?

例如,说我想用一个特定的键重置某项的某些状态值,并且它可能在字典中也可能不在字典中。我不想重置所有状态值,因此简单地用新实例替换原始项是不可行的。

这是一个人为的示例,与我的用例非常相似。您可以假设类和方法包含更多不会影响当前问题的代码。

public class MyManager
{
    private ConcurrentDictionary<int, MyWorker> _dict
        = new ConcurrentDictionary<int, MyWorker>();

    public void DoSomething1()
    {
        // ...
        _dict.GetOrAdd(1, new MyWorker(Guid.NewGuid()));
        // ...
    }

    public void DoSomething2()
    {
        // ...
        _dict.TryRemove(1, out MyWorker worker);
        // ...
    }

    public void DoSomething3()
    {
        // OPTION 1: Won't it be possible for a different thread to remove
        // the object before the Reset() call?
        var myWorker = _dict.GetOrAdd(1, new MyWorker(Guid.NewGuid()));
        myWorker.Reset();
        // OPTION 2: Does method chaining somehow guarantee execution within
        // the same lock?
        _dict.GetOrAdd(1, new MyWorker(Guid.NewGuid())).Reset();
        // OPTION 3: ?
    }
}

public class MyWorker
{
    private int _someValue = 1;
    private IList<string> _someList = new List<string>();
    private Guid _immutableValue; // should not get reset

    public MyWorker(Guid immutableValue)
    {
        _immutableValue = immutableValue;
    }

    public void Reset()
    {
        _someValue = 1;
        _someList = new List<string>();
        // NB. do not change _immutableValue!
    }
}

在此示例中,不同的线程可能以任意顺序多次调用DoSomething1()或DoSomething2(),这可能会导致在字典中随时替换键为“ 1”的MyWorker。

我需要DoSomething3()调用MyWorker.Reset()并知道如果该对象已存在于字典中,那么将使用该对象。

  1. 选项1允许在我调用Reset之前将检索到的MyWorker替换为新实例。
  2. 选项2似乎会做同样的事情,除非流利使用时进行特殊处理?

在释放锁之前,还有哪些其他选项可用于检索值并在与检索该值相同的锁内对其执行方法/操作?换句话说,如何保证原子操作?我可以通过lambda吗?

1 个答案:

答案 0 :(得分:1)

ConcurrentDictionary在保护其内部状态不受损坏的意义上是线程安全的。它不能保护其包含为键或值的对象的状态免于损坏。使用GetOrAdd方法从ConcurrentDictionary检索对象后,如果您想改变该对象的状态并且该对象不是线程安全的,则您负责同步从该对象对该对象的访问。使用锁或其他方式的多个线程。换句话说,ConcurrentDictionary类提供的线程安全保证仅限于自身。

即使它不能满足您所有的线程安全需求,您仍可能要使用此类,因为在高线程争用的情况下,其精细的内部锁定实现可能非常有效。