值类型和字典检索

时间:2009-05-11 10:12:04

标签: c# c#-3.0

下面的类为每个注册的新“dataKey”引发一个事件,并在取消注册“dataKey”时引发一个事件,并计算“dataKey为零。

这个课程的目标是线程安全,我试图尽可能提高性能。

我的问题是;在Deregister方法中,我可以在更新值时以某种方式删除第二个查找(_data [dataKey] = currentCountValue;)?

我无法简单地更新currentCountValue变量,因为该值仅在本地堆栈上更新,而不是在Dictionary中更新。

或者你可以提出任何性能改进吗?我不认为我可以删除锁并使用CAS操作(互锁方法)来更新计数,因为当这样使用时,字典对于更新不是线程安全的......对吗?

/我正在使用c#3.0。

感谢您的时间。

public sealed class DataCounter
{
    public event EventHandler NewKeyEvent;
    public event EventHandler ZeroCountEvent;
    private readonly Dictionary<string, int> _data = new Dictionary<string, int>();

    public void Register(string dataKey)
    {
        lock (_data)
        {
            if (_data.ContainsKey(dataKey))
            {
                _data[dataKey]++;
            }
            else
            {
                _data.Add(dataKey, 1);
                if (NewKeyEvent != null) NewKeyEvent(this, null);
            }
        }
    }

    public void Deregister(string dataKey)
    {
        lock (_data)
        {
            int currentCountValue;
            if (_data.TryGetValue(dataKey, out currentCountValue))
            {
                if (currentCountValue > 0)
                {
                    currentCountValue--;
                    _data[dataKey] = currentCountValue;
                }

                if (currentCountValue == 0)
                {
                    if (ZeroCountEvent != null) ZeroCountEvent(this, null);
                }
            }
        }
    }
}

3 个答案:

答案 0 :(得分:2)

作为一个想法 - 如果你不想通过索引器进行“设置”,你可以将计数器移到一个类上吗?

class CounterBox {
    public int Count {get;set;}
}

然后有一个Dictionary<string,CounterBox>。您现在可以在字典外更新Count,仅在Remove(dataKey)为零时调用.Count。这将有额外的去引用,但您不必通过索引器进行分配。

哪个更快:你需要个人资料。

类似的东西:

public sealed class DataCounter
{
    private class CounterBox
    {
        public int Count { get; set; }
    }
    public event EventHandler NewKeyEvent;
    public event EventHandler ZeroCountEvent;
    private readonly Dictionary<string, CounterBox> _data
        = new Dictionary<string, CounterBox>();

    public void Register(string dataKey)
    {
        lock (_data)
        {
            CounterBox box;
            if (_data.TryGetValue(dataKey, out box))
            {
                box.Count++;
            }
            else
            {
                _data.Add(dataKey, new CounterBox { Count = 1 });
                EventHandler handler = NewKeyEvent;
                if (handler != null) handler(this, EventArgs.Empty);
            }
        }
    }

    public void Deregister(string dataKey)
    {
        lock (_data)
        {
            CounterBox box;
            if (_data.TryGetValue(dataKey, out box))
            {
                if (box.Count > 0)
                {
                    box.Count--;
                }

                if (box.Count == 0)
                {
                    EventHandler handler = ZeroCountEvent;
                    if (handler != null) handler(this, EventArgs.Empty);
                    _data.Remove(dataKey);
                }
            }
        }
    }
}

答案 1 :(得分:0)

您的事件处理不是线程安全的。

// Execute this ...
if (NewKeyEvent != null)

// ... other threads remove all event handlers here ...

// ... NullReferenceException here.
    NewKeyEvent(this, null);

最好这样做。

EventHandler newKeyEvent = this.newKeyEvent;

 if (newKeyEvent != null)
 {
     newKeyEvent(this, null);
 }

答案 2 :(得分:0)

你应该小心你举起活动的方式(有人已经提到你的注册不是线程安全的。)

您正在调用锁内的事件处理程序。这本身并不是线程不安全,但您可能会完全停止数据结构。因为你显然无法控制你正在调用的事件处理程序中发生的事情,如果事件处理程序本身阻塞很长时间,或者冻结,你的字典将被锁定,直到处理程序返回。

在锁定中,你不应该调用你无法控制的方法,也不要调用任何执行时间不确定的方法(任何不以某种方式访问​​内存的方法)。如果这样做,即使你的代码是线程安全的,你也很容易无限期地锁定你的锁。

因此,在引用和解除引用时,您应该拥有调用列表的副本并将其称为锁定的OUTSIDE,或者在外部调用委托自身(使用Daniel提到的模式)。