我正在努力维护.NET项目,我遇到了一些麻烦,我很乐意与你们分享=)
问题代码:
if( evilDict.Count < 1 )
{
foreach (Item item in GetAnotherDict())
if (!evilDict.containsKey(item.name.ToLower().Trim()))
evilDict.add(item.name.ToLower().Trim(), item.ID);
}
尽管contains() - 检查,我收到一个ArgumentException告诉我已经添加了一个具有相同键的项目。我们只是在生产中遇到这个问题,从未进行过测试,这让我怀疑是并发问题。我想知道的是:
这是我的潜在修复,取代了dictionary.add()的东西
protected static void DictAddHelper(Dictionary<String, int> dict, String key, int value)
{
lock (dict)
{
key = key.ToLower().Trim();
if (dict.ContainsKey(key) == false)
{
try
{
dict.Add(key, value);
}
catch (ArgumentException aex)
{
StringBuilder debugInfo = new StringBuilder();
debugInfo.AppendLine("An argumentException has occured: " + aex.Message);
debugInfo.AppendLine("key = " + key);
debugInfo.AppendLine("value = " + value);
debugInfo.AppendLine("---Dictionary contains---");
foreach (String k in dict.Keys)
debugInfo.AppendLine(k + " = " + dict[k]);
log.Error(debugInfo, aex);
}
}
}
}
修改
建议不要求我进行Dict类的线程安全实现更好,因为这将是一个非常大的重构,不会是一个非常受欢迎的建议=)
EDIT2:
我试过
lock (((IDictionary)dict).SyncRoot)
但是我得到了
Error 28 Using the generic type 'System.Collections.Generic.IDictionary<TKey,TValue>' requires '2' type arguments
然后我试试这个:
lock (((IDictionary<String, int>)dict).SyncRoot)
错误:
Error 28 'System.Collections.Generic.IDictionary<string,int>' does not contain a definition for 'SyncRoot'
最终编辑(我猜):
感谢所有答案!
现在,我想知道的就是这个。我的方法(DictAddHelper)会起作用,如果没有,为什么?
答案 0 :(得分:6)
如果您怀疑访问字典时遇到并发问题,那么您的修补程序将没有任何用处。它将解决您遇到的特定问题,但是如果您同时访问该字典,将来会遇到更多问题。
在修改字典时,请考虑锁定对字典的访问。
答案 1 :(得分:3)
使用ng5000的例子,修改为建议的锁定策略会给我们:
public static class ThreadSafeDictionary
{
private static readonly Dictionary<string, int> dict = new Dictionary<string, int>();
public static void AddItem(string key, int value)
{
lock (((IDictionary)dict).SyncRoot)
{
if (!dict.ContainsKey(key))
dict.Add(key, value);
}
}
}
享受。 。 。
吉米亨德里
编辑:请注意,为了理解这个例子,类必须是静态的! ;)答案 2 :(得分:2)
第一个代码(假设Dictionary类型是System.Collections.Generic.Dictionary)将无法编译。没有public contains(也不包含Contains方法)。
那就是说,你可能会遇到并发问题。正如你暗示的另一个线程可以在ContainsKey检查之后和插入之前修改字典。要纠正这个问题,可以采用锁定语句。
有一点 - 我更希望看到包含在线程安全类中的字典,例如(警告:不完整,不打算作为参考代码,可以改进):
public class ThreadSafeDictionary
{
private Dictionary<string, int> dict = new Dictionary<string, int>();
private object padlock = new object();
public void AddItem( string key, int value )
{
lock ( padlock )
{
if( !dict.ContainsKey( key ) )
dict.Add( key, value );
}
}
}
如何实现线程安全词典已完整涵盖here。
答案 3 :(得分:2)
正如Megacan所说,您可能希望专注于解决您的解决方案中可能存在的任何并发问题。
我建议使用SynchronizedKeyedCollection,尽管这可能对你来说很重要,因为calss的成员与字典的成员不同。
答案 4 :(得分:1)
不应该
if (!evilDict.contains(item.name.ToLower().Trim()))
是
if (!evilDict.ContainsKey(item.name.ToLower().Trim()))
答案 5 :(得分:1)
很难说它是否确实是一个并发问题。 如果多个线程正在访问字典,那么它确实可能是一个并发问题,但我认为你应该添加一些跟踪代码来找出谁是罪魁祸首。
如果多个线程正在访问字典,那么您应该确保在一个原子事务中调用Contains(或ContainsKey)和Add方法。 为了做到这一点,你应该确实在锁中调用这两个方法。
lock( dict.SyncRoot )
{
if( dict.Contains( .. ) == false )
dict.Add ( );
}
答案 6 :(得分:1)
lock (((IDictionary)dict).SyncRoot)
{
if(!dict.Contains( .. ))
dict.Add ( );
}
这对我有用(请参阅下面的更新ng5000示例):)
答案 7 :(得分:1)
.NET Framework中内置了一个线程安全的字典类,它已经为解决您的问题提供了良好的开端,这可能确实与并发相关。
这是一个名为SynchronizedKeyedCollection(K, T)的抽象类,您可以从中派生并添加一个包含对Contains的调用的方法,然后在锁定base.SyncRoot的锁内添加。
答案 8 :(得分:1)
Ace - 奇怪的是它对你的代码不起作用,它肯定按照例子(我知道这对你没有帮助,只是一个好奇心!)。
希望你能解决这个问题,我相信这是失败的一个相当简单的原因。你甚至可能想尝试代替你的代码来看看它是否是其他问题。
答案 9 :(得分:1)
度Acc,
创建一个新的WindowsFormsApplication(.Net 2.0)并将代码粘贴到Form1.cs代码中。 (您可能必须将命名空间的名称从WindowsFormsApplication1更改为系统默认的名称)。此外,向窗体添加一个命令按钮。无论如何,这一切都是:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private readonly Dictionary<string, int> localDict1 = new Dictionary<string, int>();
private readonly Dictionary<string, string> localDict2 = new Dictionary<string, string>();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// use two different dictionaries just to show it working with
// different data types (i.e. we could use class objects too)
if (localDict1 != null)
{
ThreadSafeDictionaryStatic.AddItem(localDict1, "New Item :1", 1);
ThreadSafeDictionaryStatic.AddItem(localDict1, "New Item :2", 2);
}
if (localDict2 != null)
ThreadSafeDictionaryStatic.AddItem(localDict2, "New Item :1", "this is not a number");
}
}
public static class ThreadSafeDictionaryStatic
{
public static void AddItem<T>(IDictionary<string, T> dict, string key, T value)
{
if (dict == null) return;
lock (((IDictionary)dict).SyncRoot)
{
if (!dict.ContainsKey(key))
dict.Add(key, value);
else
{
// awful and we'd never ever show a message box in real life - however!!
var result = dict[key];
MessageBox.Show(String.Format("Key '{0}' is already present with a value of '{1}'", key, result));
}
}
}
public static T GetItem<T>(IDictionary<string, T> dict, string key)
{
if (dict == null) return default(T);
if (dict.ContainsKey(key))
return dict[key];
else
return default(T);
}
public static bool Remove<T>(IDictionary<string, T> dict, string key)
{
if (dict == null) return false;
lock (((IDictionary)dict).SyncRoot)
{
if (dict.ContainsKey(key))
return dict.Remove(key);
}
return false;
}
}
}
让我知道这是怎么回事......
编辑:重新上课以匹配您的方法签名,也使用泛型,因为您希望该方法接受任何类型的对象。您可以通过删除<T>
引用等来轻松更改它。
答案 10 :(得分:0)
重构不一定非常痛苦或难以执行。只需进行以下重构:
1)为您的字典创建一个包装器容器类,它实现与字典
相同的接口2)寻找你的字典的声明并应用一个重构改变声明的类型(到你刚创建的那个)
3)尝试构建,此时,如果你的包装器实现了所有的接口成员,你不应该得到任何编译错误
4)在您的词典的访问者上,将所有内容包装在锁中或您想要应用的任何同步策略中