C# 什么';实现线程安全字典的最佳方法是什么?

C# 什么';实现线程安全字典的最佳方法是什么?,c#,.net,thread-safety,collections,C#,.net,Thread Safety,Collections,通过从IDictionary派生并定义私有SyncRoot对象,我能够在C#中实现线程安全字典: public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue> { private readonly object syncRoot = new object(); private Dictionary<TKey, TValue> d = new Dictionary<

通过从IDictionary派生并定义私有SyncRoot对象,我能够在C#中实现线程安全字典:

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public object SyncRoot
    {
        get { return syncRoot; }
    } 

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
    }

    // more IDictionary members...
}

我能够使它工作,但这导致了一些丑陋的代码。我的问题是,有没有更好、更优雅的方法来实现线程安全字典?

您不需要在使用者对象中锁定SyncRoot属性。您在字典的方法中拥有的锁就足够了

详细说明: 最终发生的情况是,您的词典被锁定的时间比需要的时间更长

在您的情况下会发生以下情况:

假设线程A在调用m_mySharedDictionary.Add之前获得了SyncRoot上的锁。线程B随后尝试获取锁,但被阻止。事实上,所有其他线程都被阻塞了。允许线程A调用Add方法。在Add方法中的lock语句中,允许线程A再次获取锁,因为它已经拥有它。在退出方法内部和方法外部的锁上下文时,线程A已释放所有锁,允许其他线程继续


您可以简单地允许任何使用者调用Add方法,因为SharedDictionary类Add方法中的lock语句将具有相同的效果。此时,您有冗余锁定。如果必须对dictionary对象执行两个需要保证连续发生的操作,则只能在其中一个dictionary方法之外锁定SyncRoot。

正如Peter所说,可以将所有线程安全性封装在类中。您需要小心处理您公开或添加的任何事件,确保在任何锁之外调用它们

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
        OnItemAdded(EventArgs.Empty);
    }

    public event EventHandler ItemAdded;

    protected virtual void OnItemAdded(EventArgs e)
    {
        EventHandler handler = ItemAdded;
        if (handler != null)
            handler(this, e);
    }

    // more IDictionary members...
}
公共类安全字典:IDictionary
{
私有只读对象syncRoot=新对象();
私有字典d=新字典();
公共无效添加(TKey键,TValue值)
{
锁定(同步根)
{
d、 添加(键、值);
}
OnItemed(eventags.Empty);
}
添加了公共事件事件处理程序项;
受保护的虚拟服务器(事件参数e)
{
EventHandler=ItemAdded;
if(处理程序!=null)
处理者(本,e);
}
//更多IDictionary成员。。。
}

编辑:MSDN文档指出枚举本质上不是线程安全的。这可能是在类外部公开同步对象的原因之一。另一种方法是提供一些对所有成员执行操作的方法,并锁定成员的枚举。问题是,您不知道传递给该函数的操作是否调用字典的某个成员(这将导致死锁)。公开同步对象允许使用者做出这些决定,并且不会在类中隐藏死锁。

您不应该通过属性发布私有锁对象。锁定对象应单独存在,以用作集合点


如果使用标准锁时性能不佳,那么Wintellect的锁集合将非常有用。

您描述的实现方法存在几个问题

  • 您不应该公开同步对象。这样做会让你向消费者敞开心扉,消费者抓住物品并锁定它,然后你就完蛋了
  • 您正在使用线程安全类实现一个非线程安全接口。我想这会让你在路上付出什么代价

  • 就我个人而言,我发现实现线程安全类的最佳方法是通过不变性。它确实减少了线程安全性可能遇到的问题数量。查看更多详细信息

    尝试内部同步几乎肯定是不够的,因为它的抽象级别太低了。假设您将
    Add
    ContainsKey
    操作分别设置为线程安全,如下所示:

    public void Add(TKey key, TValue value)
    {
        lock (this.syncRoot)
        {
            this.innerDictionary.Add(key, value);
        }
    }
    
    public bool ContainsKey(TKey key)
    {
        lock (this.syncRoot)
        {
            return this.innerDictionary.ContainsKey(key);
        }
    }
    
    那么,当您从多个线程调用这段假定是线程安全的代码时,会发生什么呢?它会一直正常工作吗

    if (!mySafeDictionary.ContainsKey(someKey))
    {
        mySafeDictionary.Add(someKey, someValue);
    }
    
    简单的答案是否定的。在某个时刻,
    Add
    方法将抛出一个异常,指示该键已经存在于字典中。你可能会问,线程安全字典怎么会这样?因为每个操作都是线程安全的,所以两个操作的组合不是,因为另一个线程可以在调用
    ContainsKey
    Add
    之间对其进行修改

    这意味着要正确编写这种类型的场景,您需要字典之外的锁,例如

    lock (mySafeDictionary)
    {
        if (!mySafeDictionary.ContainsKey(someKey))
        {
            mySafeDictionary.Add(someKey, someValue);
        }
    }
    
    但是现在,由于您必须编写外部锁定代码,您将内部和外部同步混淆,这总是导致代码不清晰和死锁等问题。因此,最终您可能会更好地选择:

  • 使用普通的
    字典
    并在外部同步,包括其上的复合操作,或

  • 编写一个新的线程安全包装器,使用不同的接口(即非
    IDictionary
    )组合操作,例如
    AddifyNotContained
    方法,这样您就不需要组合来自它的操作


  • (我倾向于自己去)

    支持并发的.NET 4.0类被命名。

    只是想一想,为什么不重新创建字典呢?如果读取是大量写入,那么锁定将同步所有请求

    范例

        private static readonly object Lock = new object();
        private static Dictionary<string, string> _dict = new Dictionary<string, string>();
    
        private string Fetch(string key)
        {
            lock (Lock)
            {
                string returnValue;
                if (_dict.TryGetValue(key, out returnValue))
                    return returnValue;
    
                returnValue = "find the new value";
                _dict = new Dictionary<string, string>(_dict) { { key, returnValue } };
    
                return returnValue;
            }
        }
    
        public string GetValue(key)
        {
            string returnValue;
    
            return _dict.TryGetValue(key, out returnValue)? returnValue : Fetch(key);
        }
    
    private static readonly object Lock=new object();
    私有静态字典_dict=new Dictionary();
    私有字符串获取(字符串密钥)
    {
    锁(锁)
    {
    字符串返回值;
    if(_dict.TryGetValue(键,输出返回值))
    返回值;
    returnValue=“查找新的va
    
        private static readonly object Lock = new object();
        private static Dictionary<string, string> _dict = new Dictionary<string, string>();
    
        private string Fetch(string key)
        {
            lock (Lock)
            {
                string returnValue;
                if (_dict.TryGetValue(key, out returnValue))
                    return returnValue;
    
                returnValue = "find the new value";
                _dict = new Dictionary<string, string>(_dict) { { key, returnValue } };
    
                return returnValue;
            }
        }
    
        public string GetValue(key)
        {
            string returnValue;
    
            return _dict.TryGetValue(key, out returnValue)? returnValue : Fetch(key);
        }