值类型和字典检索

3
下面的类为每个新注册的“dataKey”引发事件,并在注销“dataKey”且该“dataKey”的计数为零时引发事件。
该类旨在是线程安全的,并且我正在尽可能使其性能最佳。
我的问题是,在Deregister方法中,当我更新值(_data[dataKey] = currentCountValue;)时,我是否可以以某种方式删除第二次查找?
我无法简单地更新currentCountValue变量,因为该值仅在本地堆栈上更新,而不在字典中。
或者您能否建议任何性能改进? 我认为我不能删除锁并使用CAS操作(Interlocked方法)来更新计数,因为字典在像这样使用时不是线程安全的...对吗?
我正在使用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个回答

2
作为一种思路 - 如果你不想通过索引器进行"设置",你可以将计数器移到一个类上?
class CounterBox {
    public int Count {get;set;}
}

然后有一个Dictionary<string,CounterBox>。您现在可以在字典外更新Count,并仅在.Count为零时调用Remove(dataKey)。这将有一个额外的去引用,但您不必通过索引器进行分配。
至于哪个更快:您需要进行性能分析。
类似这样的东西:
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);
                }
            }
        }
    }
}

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);
 }

0

在引发事件的方式上,你需要小心(有人已经提到你的注册不是线程安全的)。

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

在锁定期间,你绝不能调用任何你无法控制的方法,也不能调用任何执行时间不确定的方法(任何不以某种方式访问内存的方法)。如果你这样做,即使你的代码是线程安全的,你也容易遭受锁定无限期阻塞的风险。

因此,在引用和取消引用时,你应该要么拥有一个调用列表的副本并在锁定外部调用它,要么在锁定外部调用委托本身(使用Daniel提到的模式)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接