C# 如何锁定字典,然后安全地将锁转移到字典中的某个值?

C# 如何锁定字典,然后安全地将锁转移到字典中的某个值?,c#,multithreading,synchronization,C#,Multithreading,Synchronization,我正在尝试找到一种安全的方法来同步对嵌套字典的访问,其中外部字典上的操作会锁定对整个集合的任何操作。然而,一旦检索到内部字典,我想释放外部锁,只防止线程操纵内部字典 一旦外部字典中存在内部字典,就可以使用lock关键字进行此操作,但是,我正在努力找到一种安全的方法来添加和填充内部字典,而不引入竞争条件。假设“填充”内部字典将是一项昂贵的操作。这就是为什么在外部字典的lock下执行此操作不是一个选项。当对新的内部字典执行“填充”时,其他线程必须具有对外部字典的访问权限,才能对其他内部字典执行操作

我正在尝试找到一种安全的方法来同步对嵌套字典的访问,其中外部字典上的操作会锁定对整个集合的任何操作。然而,一旦检索到内部字典,我想释放外部锁,只防止线程操纵内部字典

一旦外部字典中存在内部字典,就可以使用
lock
关键字进行此操作,但是,我正在努力找到一种安全的方法来添加和填充内部字典,而不引入竞争条件。假设“填充”内部字典将是一项昂贵的操作。这就是为什么在外部字典的
lock
下执行此操作不是一个选项。当对新的内部字典执行“填充”时,其他线程必须具有对外部字典的访问权限,才能对其他内部字典执行操作

我在下面列举了一些例子,这些例子可以更好地说明我的问题

  • 我认为这是一个引入竞争条件的简单方法
  • 然而,我相信一种方法会奏效,它会导致不必要地填充多个内部字典。这只会使用第一个赢得比赛的内部字典
  • 我相信这种方法会奏效,但这是一个陌生的领域。即使进行了大量的测试,我也会担心我缺乏使用C#的
    监视器
    对象的经验可能会导致意外的后果
    示例1:我认为解决问题的方法不安全。然而,这就是我对同步的理解。我更愿意使用这种方法,但是我不知道如何使用
    lock
    关键字来实现我需要的行为

    public void SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
    {
        Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
    
        lock (outerDictionary)
        {
            // No need to add a new innerDictionary, it already exists
            if (outerDictionary.ContainsKey(newKey))
            {
                return;
            }
    
            innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
            outerDictionary.Add(newKey, innerDictionary);
        }
    
        // I want to allow other threads to have access to outerDictionary
        // However, I don't want other threads working against THIS innerDictionary
        lock (innerDictionary)
        {
            // Here lies my concern with this approach. Another thread could have
            //   taken the lock for innerDictionary. Doing this all under the lock
            //   for outerDictionary would be safe but would prevent access to other
            //   inner dictionaries while expensive operations are performed only
            //   pertaining to THIS innerDictionary
            this.PopulateInnerDictionary(innerDictionary);
        }
    }
    
    示例3:我认为这是示例1中演示的潜在同步问题的解决方案。但是,我不熟悉使用
    监视器
    模式,非常感谢您的反馈

    public void SyncronizationExample3(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
    {
        Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
        bool aquiredLockForOuterDictionary = false;
        bool aquiredLockForInnerDictionary = false;
    
        try
        {
            Monitor.Enter(outerDictionary, ref aquiredLockForOuterDictionary);
    
            if (outerDictionary.Contains(newKey)
            {
                // No need to add a new innerDictionary, it already exists
                return;
            }
    
            innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
            outerDictionary.Add(newKey, innerDictionary);
    
            // This is where I "handoff" the lock to innerDictionary to alleviate my concern 
            //   in Example 1 where another thread could steal the innerDictionary lock 
            Monitor.Enter(innerDictionary, ref aquiredLockForInnerDictionary);
        }
        finally
        {
            // I read that this bool pattern was preferred for .net 4+, 
            //   however I am unsure if this is the best practice
            if (aquiredLockForOuterDictionary)
            {
                Monitor.Exit(dictionary);
            }
        }
        try
        {
            if (!aquiredLockForInnerDictionary)
            {
                // An exception must have occurred prior to or during the acquisition
                //   of this lock. Not sure how I'd handle this yet but
                //   I'm pretty shit out of luck.
                return;
            }
    
            // Here I would perform an expensive operation against the innerDictionary
            //   I do not want to lock consumers form accessing other innerDictionaries 
            //   while this computation is done. 
            this.PopulateInnerDictionary(innerDictionary);
        }
        finally
        {
            // I need to check this here incase an exception in the first  
            //   try finally prevented this from being acquired 
            if (aquiredLockForInnerDictionary)
            {
                Monitor.Exit(innerDictionary);
            }
        }
    }
    
    public void SyncronizationExample3(Dictionary outerDictionary,TKey newKey)
    {
    Dictionary innerDictionary=null;
    bool-aquiredLockForOuterDictionary=false;
    bool-aquiredLockForInnerDictionary=false;
    尝试
    {
    Monitor.Enter(outerDictionary,ref-aquiredLockForOuterDictionary);
    if(outerDictionary.Contains)(newKey)
    {
    //无需添加新的innerDictionary,它已存在
    返回;
    }
    innerDictionary=新字典();
    Add(newKey,innerDictionary);
    //这就是我将锁“移交”到innerDictionary的地方,以减轻我的担忧
    //在示例1中,另一个线程可以窃取innerDictionary锁
    Monitor.Enter(innerDictionary,ref aquiredLockForInnerDictionary);
    }
    最后
    {
    //我读到这个bool模式是.net 4+的首选模式,
    //但是,我不确定这是否是最佳做法
    if(针对外部词典的AquiredLock)
    {
    监控退出(字典);
    }
    }
    尝试
    {
    如果(!aquiredLockForInnerDictionary)
    {
    //收购之前或期间必须发生异常
    //我还不知道该怎么处理这个锁但是
    //我真倒霉。
    返回;
    }
    //在这里,我将对innerDictionary执行代价高昂的操作
    //我不想锁定访问其他InnerDictionary的使用者
    //当这个计算完成时。
    这是一本流行词典(innerDictionary);
    }
    最后
    {
    //我需要在这里检查一下,以防第一次出现异常
    //try最终阻止了这种情况的发生
    if(aquiredLockForInnerDictionary)
    {
    Monitor.Exit(内部字典);
    }
    }
    }
    
    似乎您想得太多了。我唯一的问题是,您是否有可靠的方法知道是否已填充内部字典实例?我假设
    Count
    属性的非零值就足够了,不是吗

    根据该假设,您可以执行以下操作:

    public Dictionary<TKey2, ReferenceTypedValues> SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
    {
        Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
    
        lock (outerDictionary)
        {
            // No need to add a new innerDictionary if it already exists
            if (!outerDictionary.TryGetValue(newKey, out innerDictionary))
            {
                innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
                outerDictionary.Add(newKey, innerDictionary);
            }    
        }
    
        // Found the inner dictionary, but might be racing with another thread
        // that also just found it. Lock and check whether it needs populating
        lock (innerDictionary)
        {
            if (innerDictionary.Count == 0)
            {
                this.PopulateInnerDictionary(innerDictionary);
            }
        }
    
        return innerDictionary;
    }
    
    public Dictionary SyncronizationExample1(Dictionary outerDictionary,TKey newKey)
    {
    Dictionary innerDictionary=null;
    锁(外字典)
    {
    //如果innerDictionary已经存在,则无需添加新的innerDictionary
    if(!outerDictionary.TryGetValue(newKey,out innerDictionary))
    {
    innerDictionary=新字典();
    Add(newKey,innerDictionary);
    }    
    }
    //找到了内部字典,但可能正在与其他线程竞争
    //它也刚刚找到它。锁定并检查它是否需要填充
    锁(内部字典)
    {
    if(innerDictionary.Count==0)
    {
    这是一本流行词典(innerDictionary);
    }
    }
    返回内部字典;
    }
    
    注:

    • 将对象本身用作锁对象不是一个好主意,因为您可能会遇到一些其他代码具有相同的坏主意并与该代码死锁的风险。相反,请存储一个复合值(例如,
      Tuple
      ,一个自定义命名结构,等等)它既包含用于锁定的
      对象
      ,也包含内部字典本身(当然,在字段中存储一个专用对象,用于锁定单个外部字典)
    • 你的问题没有描述这些对象是如何使用的。我猜一旦填充,它们是只读的?如果是这样,上面的内容应该可以,但你应该使用类来强制执行。否则,你当然也需要为此添加同步
    • 即使假设它们是只读的,您的原始代码实际上也不会返回内部字典引用
      public Dictionary<TKey2, ReferenceTypedValues> SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
      {
          Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
      
          lock (outerDictionary)
          {
              // No need to add a new innerDictionary if it already exists
              if (!outerDictionary.TryGetValue(newKey, out innerDictionary))
              {
                  innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
                  outerDictionary.Add(newKey, innerDictionary);
              }    
          }
      
          // Found the inner dictionary, but might be racing with another thread
          // that also just found it. Lock and check whether it needs populating
          lock (innerDictionary)
          {
              if (innerDictionary.Count == 0)
              {
                  this.PopulateInnerDictionary(innerDictionary);
              }
          }
      
          return innerDictionary;
      }