【问题标题】:C# System.Collections.Generic.Dictionary`2.Insert - An item with the same key has already been addedC# System.Collections.Generic.Dictionary`2.Insert - 已添加具有相同键的项
【发布时间】:2016-10-12 16:50:27
【问题描述】:

这个问题是关于 .Net Framework 4.5 MVC Web 应用程序的。

我有一段我们继承并使用多年的代码块,它通常将 DataTable 转换为 List,其中一个私有方法获取泛型类的属性列表,例如:

原始代码

    private static Dictionary<Type, IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();

    public static IList<PropertyInfo> GetPropertiesForType<T>()
    {
        //variables
        var type = typeof(T);

        //get types
        if (!typeDictionary.ContainsKey(typeof(T)))
        {
            typeDictionary.Add(type, type.GetProperties().ToList());
        }

        //return
        return typeDictionary[type];
    }


那里没有什么令人兴奋的事情,它只是确保 typeDictionary 尚未包含键(类型)并将其添加到字典中(键=类型,值=属性),以便我们以后可以访问它们。

我们通常将此代码用于任何类型的“模型”对象,但对于这个特定示例,这是在 2 个不同场合给我带来麻烦的那个。

模型对象

public class GetApprovalsByUserId
{
    // constructor
    public GetApprovalsByUserId()
    {
        TicketId = 0;
        ModuleName = string.Empty;
        ModuleIcon = string.Empty;
        ApprovalType = string.Empty;
        VIN = string.Empty;
        StockNumber = string.Empty;
        Year = 0;
        Make = string.Empty;
        Model = string.Empty;
        Services = string.Empty;
        RequestedDate = DateTime.MinValue;
    }

    // public properties
    public int TicketId { get; set; }
    public string ModuleName { get; set; }
    public string ModuleIcon { get; set; }
    public string ApprovalType { get; set; }
    public string VIN { get; set; }
    public string StockNumber { get; set; }
    public int Year { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
    public string Services { get; set; }
    public DateTime RequestedDate { get; set; }
}


同样,在那个特定的模型类中没有什么真正重要的事情发生,与我们在任何其他类中使用的没有什么不同。

就像我说的那样,我们在多个项目中普遍使用此代码,并且从未遇到过问题,但在过去一天的 2 次不同的情况下,我们让它抛出了以下异常:

已添加具有相同密钥的项目。

at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at Utilities.Extensions.GetPropertiesForType[T]()
at Utilities.Extensions.ToObject[T](DataRow row)
at Utilities.Extensions.ToList[T](DataTable table)


如果有帮助,您可以在此处查看扩展方法所在的完整 Extensions.cs 类(静态):

https://pavey.azurewebsites.net/resources/Extensions.txt

我的问题是:

  1. 考虑到代码已经在进行 !typeDictionary.ContainsKey(typeof(T)) 检查,它怎么可能通过该测试,却失败了typeDictionary.Add(type, type.GetProperties().ToList()); 调用?

  2. 为什么会如此零星?它似乎 99% 的时间都在工作,使用相同的代码、相同的类(GetApprovalsByUserId,如上所示),并且在任何其他项目或任何其他模型类中从未失败过。

我们无法在任何环境中使用完全相同的代码、模型、数据或其他完全相同的设置重现此问题,因此不知道如何再保护此代码比现在要好。

我的一个想法是将代码更改为:

提议的代码更改

    private static Dictionary<Type, IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();

    public static IList<PropertyInfo> GetPropertiesForType<T>()
    {
        //variables
        var type = typeof(T);
        IList<PropertyInfo> properties = null;

        //get types
        try
        {
            if (!typeDictionary.ContainsKey(type))
            {
                typeDictionary.Add(type, type.GetProperties().ToList());
            }
        }
        catch
        {
        }

        // try get value
        typeDictionary.TryGetValue(type, out properties);

        // return
        return properties;
    }


但是由于我一开始就无法重现该错误,所以我也不完全确定这是否是防弹的。我的想法是 ContainsKey 有点奇怪,特别是使用 typeof(T) 作为“键”时,它允许它在奇怪的情况下通过测试,而实际上它不应该通过,但是 Add 失败,因为它知道密钥已经存在。因此,如果我尝试/捕获它,如果 ContainsKey 错误地告诉我它不存在,而实际上是,则 Add 仍然会失败,但我会捕获它并继续前进,然后我可以 TryParse 获取值出来了,一切都会好起来的。

感谢任何想法、想法,或具体如何使用上面显示的原始代码重现问题,并建议改进以保护它。

【问题讨论】:

  • Given the fact that the code is already doing a !typeDictionary.ContainsKey(typeof(T)) check, how is it possible that it could ever pass that test, yet fail on the typeDictionary.Add(type, type.GetProperties().ToList()); call? -- 比赛条件?
  • Why would it be so sporadic? It seemingly works 99% of the time, using the same code, the same class (GetApprovalsByUserId, shown above), and never has failed otherwise in any other project or any other model class. -- 比赛条件?
  • 绝对是竞争条件。您需要在检查密钥是否已存在并插入它的代码周围lock
  • 罗伯特和马特,对此非常有帮助的见解。我想在代码更改之前/之后没有很好的方法来模拟/测试它以证明它排除了合理怀疑?
  • 哦,模拟竞态条件非常容易,只是不在您的应用程序中。下面的大部分代码都是正统的,可以放心使用,但如果你真的需要证明,把它放在一个小的控制台应用程序中,然后抛出几个线程。

标签: c# .net generics dictionary static


【解决方案1】:

您遇到的问题是并发访问。在检查和插入字典之间,另一个线程进来并添加了类型,导致第二次插入失败。

要解决此问题,您有两种选择:使用锁(如其他答案中所述)或使用 ConcurrentCollection:

using System.Collections.Concurrent;
private static ConcurrentDictionary<Type, IList<PropertyInfo>> typeDictionary = new ConcurrentDictionary<Type, IList<PropertyInfo>>();

public static IList<PropertyInfo> GetPropertiesForType<T>()
{
    //variables
    var type = typeof(T);

    typeDictionary.TryAdd(type, type.GetProperties().ToList());

    //return
    return typeDictionary[type];
}

如果该值尚不存在,则添加该值并返回true,否则将不执行任何操作并返回false

【讨论】:

【解决方案2】:

你需要一个对象来锁定:

private object lockObj = new object();

然后你需要在添加密钥之前锁定:

if (!typeDictionary.ContainsKey(typeof(T)))
{
    lock(lockObj) 
    {
        if (!typeDictionary.ContainsKey(typeof(T)))
        {
            typeDictionary.Add(type, type.GetProperties().ToList());
        }
    }
}

如果它已经在添加过程中,这将使正在寻找相同密钥的任何其他线程等待。您再次检查锁内的ContainsKey,因为当一个停止的线程最终获得锁时,另一个线程可能已经插入了密钥。

这是double check locking的示例

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-29
    • 1970-01-01
    • 2021-05-14
    • 2017-08-20
    相关资源
    最近更新 更多