使用C#中的数据库值填充ConcurrentDictionary的锁定注意事项

时间:2012-03-23 17:40:39

标签: c# multithreading generics thread-safety

当我在这里填写ConcurrentDictionary时,是否需要使用lock(lockObj){}块?对于一些小背景,这将用于MVC应用程序,但我怀疑场景问题与任何多线程应用程序相关。

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

1)将List对象值设置为锁定的私有静态是否会被认为是更好的做法,希望在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();

4 个答案:

答案 0 :(得分:3)

你肯定得到的并不安全。考虑:

线程X和Y几乎同时调用GetOptionById。需要填充字典的X点,并开始这样做。第一个结果返回,并添加到字典中。

Y然后发现有一个条目,并假设字典已经完成 - 所以它会获取它感兴趣的选项,这可能不是已经加载的那个。

这看起来像是使用Lazy<T>的一个很好的候选者...你可以在那里选择适当的选项,这样一次只能有一个线程填充字典 - 第二个线程会等到第一个线程完成在继续之前。这样,“填充字典”实际上变成了原子。

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

答案 1 :(得分:2)

您的代码中可能会出现以下问题。如果它们是可以接受的,那么你没关系,如果不是,那么你就需要使用锁。

  1. 多个线程可以实现options null并重新创建字典。这将导致它 多次填充。
  2. 可以从中读取线程 一些但不是全部的项目都是字典 加入。

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

  

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

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

  

1)创建List对象值是否被认为是更好的做法   一个私人的静态,你锁定,希望不要打电话给   在ConcurrentDictionary填充之前多次出现数据库?

也许,特别是如果GetList本身不是线程安全的话。除了你提出的建议不起作用。 List<MyBusinessObject>实例从GetList返回,因此您无法锁定尚不存在的内容。相反,您将创建一个单独的对象仅用于锁定目的。

  

2)是否(上面的#1)甚至是必需的或者是ConcurrentDictionary   聪明到可以自己解决这个问题吗?

不,没有任何魔法会以某种方式导致GetList连续执行。

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