2012-03-23 138 views
3

当我在此处填写ConcurrentDictionary时,是否有必要使用锁定(lockObj){}块?尽管我怀疑这个场景问题与任何多线程应用程序都有关系,但是在MVC应用程序中会使用它。使用C#中的数据库值填充ConcurrentDictionary的锁定注意事项

在搜索stackoverflow我没有找到这个确切的情况。在第一次从GetOptionById值请求值时,它可以被两个单独的线程调用。

1)难道被认为是更好的做法,使对象列表值,可以在ConcurrentDictionary数据库之前多次没有要求的希望锁定周围充满私人静态的?

2)(即上面的#1)甚至是必要的,还是ConcurrentDictionary能够聪明地完成它自己的工作?预先感谢您的任何意见。

public class MyOptions 
{ 
    static string GetOptionById(int id) 
    { 
     if (options == null || options.Count <= 0) 
      FillOptionList(); 
     return options[id]; 
    } 

    static void FillOptionList() 
    { 
     List<MyBusinessObject> objects = DataAccessLayer.GetList(); 
     foreach (MyBusinessObject obj in objects) 
      options.TryAdd(obj.Id, obj.Name); 
    } 

    private static ConcurrentDictionary<int, string> options = new ConcurrentDictionary<int, string>(); 
} 

编辑:谢谢大家的意见,这会是一个更安全的方法吗?

public static string OptionById(int id) 
    { 
     if (!options.ContainsKey(id)) 
     { 
      //perhaps this is a new option and we need to reload the list 
      FillOptionsOrReturn(true /*force the fill*/); 
      return (!options.ContainsKey(id)) ? "Option not found" : options[id]; 
     } 
     else 
      return options[id]; 
    } 

    private static void FillOptionsOrReturn(bool forceFill = false) 
    { 
     List<MyBusinessClass> objectsFromDb = null; 
     lock (lockObj) 
     { 
      if (forceFill || options == null || options.Keys.Count <= 0) 
       reasons = DataAccessLayer.GetList(); 
     } 
     if (objectsFromDb != null) 
     { 
      foreach (MyBusinessClass myObj in objectsFromDb) 
       options.TryAdd(myObj.id, myObj.name); 
     } 
    } 

    private static ConcurrentDictionary<int, string> options = new ConcurrentDictionary<int, string>(); 
    private static object lockObj = new object(); 

回答

1

当我在这里填写我的ConcurrentDictionary时,是否需要使用锁(lockObj){}块?

不,这个数据结构上的方法已经是线程安全的。

1)难道被认为是更好的做法,使对象列表值 您在ConcurrentDictionary前 数据库多次不叫的希望锁定围绕一个私有静态填充?

也许,特别是如果GetList本身不是线程安全的。除了你提出的建议是行不通的。那List<MyBusinessObject>实例从GetList返回,所以你不能锁定一些还不存在的东西。相反,您只能为锁定目的创建一个单独的对象。

2)是(上面#1)甚至是必要的吗?或者是ConcurrentDictionary 足够聪明来解决它自己的问题吗?

不,没有什么魔法会导致GetList连续执行。

顺便说一句,您的GetOptionById有一个竞争条件。多个线程可以同时进入if块。您的代码可能会尝试多次初始化字典。

3

你有什么绝对不安全。试想一下:

线程X和Y两个呼叫GetOptionById在大致相同的时间。它需要填充词典的X点,并开始这样做。第一个结果返回,并被添加到字典中。

然后发现有一个条目,并且假定字典是完整的 - 因此它获取它感兴趣的选项,这可能是不是那个已经加载的选项。

这看起来很适合使用Lazy<T> ......您可以在那里选择适当的选项,以便每次只有一个线程可以填充字典 - 第二个线程将等到第一个线程完成后再继续。这样,“填充字典”就变成了原子。

如果您在第一次加载后永远不需要更新字典,那么您甚至可以只用Lazy<Dictionary<string, string>>即可 - 只要没有写入者,可以安全地拥有多个阅读器。我相信Lazy<T>将适当地处理内存障碍。

+0

伟大的输入,很可能是字典需要更新,但不经常(也许每天一次),在这种情况下,将使我从数据库中填充的惰性对象集合私有和静态到我的类将是最好的方法? – likestoski 2012-03-23 17:51:45

+0

@ likestoski:使用'懒惰'仍然是值得的,如果你需要更新字典,那么保持它作为一个ConcurrentDictionary是一个很好的计划。我不确定你的下一部分是什么意思 - 除非你打算阻止*更新*同时发生(是什么触发它们?),我不认为有什么特别的需要。 – 2012-03-23 17:58:49

2

在您的代码中可能会发生以下问题。如果他们可以接受,那么你没事,如果他们不是,那么你需要使用锁。

  1. 多线程可能认识到options为 为空并重新创建字典。这将导致它被多次填充 。
  2. 这是可能的线程从 读字典,而有些,但不是全部的项目已经 增加。
1

ConcurrentDictionary只提供用于访问列表元素的线程安全性。另一方面,您的FillOptionList方法可以从不同的线程多次调用,所有这些方法都可以愉快地将值插入到集合中。

你需要锁定以避免这种情况不是集合本身,而是GetOptionById中的条件检查。