首页 > 解决方案 > 无法在 C# ConcurrentDictionary 上调用 PropertyChanged 事件

问题描述

每当将新项目添加到字典中时,我都尝试使用ConcurrentDictionaryinC#并处理事件,但我无法这样做,代码如下

public class TopicTaskConcurrentDictionary
    {
        #region Singleton
        private static volatile ConcurrentDictionary<KeyValuePair<string, string>, IDataPipesService> _instance;
        private static readonly object Sync = new object();
        private static readonly object Lock = new object();

        public static event EventHandler  PropertyChanged;

        public static ConcurrentDictionary<KeyValuePair<string, string>, IDataService> Instance
        {
            get
            {
                if (_instance != null) return _instance;
                lock (Sync)
                {
                    if (_instance == null)
                    {
                        _instance = new ConcurrentDictionary<KeyValuePair<string, string>, IDataService>();
                    }
                }
                return _instance;
            }
        }
        #endregion Singleton

        public  void TryAdd(KeyValuePair<string, string> keyValuePair, IDataPipesService service) {
            Instance.TryAdd(keyValuePair, service);
            PropertyChanged(null, EventArgs.Empty);
        }
    }

这是我添加到字典的方式

  var dataService = _kernel.Get<IDataService>();
TopicTaskConcurrentDictionary.Instance.TryAdd(new KeyValuePair<string, string>(param.TagPrefix, param.TopicName), dataService);

向字典中添加项目时,我希望调用如下ProperyChanged事件

TopicTaskConcurrentDictionary.PropertyChanged += delegate (object o, EventArgs e)
            {
                foreach (var item in TopicTaskConcurrentDictionary.Instance) {
                    if (!item.Value.Running)
                        //do something
                }
            };

当我执行上述操作时,PropertyChanged永远不会调用事件,我可以知道我哪里出错了吗?

标签: c#event-handlingconcurrentdictionary

解决方案


您遇到了封装问题。似乎您错误地实现了单例模式。您正在将基础集合公开给客户端代码。这绕过了TopicTaskConcurrentDictionary逻辑。这就是为什么您的事件永远不会被提出的原因。您实际上是在调用ConcurrentDictionary.TryAdd而不是TopicTaskConcurrentDictionary.TryAdd.

您正在将新项目直接添加到基础集合中。要修复您的代码,请删除该Instance属性或正确实施 Singleton。但是两个版本都应该至少实现IEnumerable<T>接口。

我替换PropertyChangedCollectionChanged(它启用ObservableCollection绑定场景中的行为)并添加了一个实现IEnumerable<T>.GetEnumerator()以使集合能够在foreach迭代中使用。然后我正确地实现了单例模式。通过使用Lazy<T>,您可以显着减少创建单个共享线程安全实例的代码:

public sealed class TopicTaskConcurrentDictionary : IEnumerable, IEnumerable<KeyValuePair<KeyValuePair<string, string>, IDataPipesService>>
{
  public static TopicTaskConcurrentDictionary Instance => 
    TopicTaskConcurrentDictionary._instance.Value;

  public event NotifyCollectionChangedEventHandler CollectionChanged;    

  private ConcurrentDictionary<KeyValuePair<string, string>, IDataPipesService> underlyingCollection;
  private static readonly object Sync = new object();    
  private static readonly Lazy<TopicTaskConcurrentDictionary> _instance = 
    new Lazy<TopicTaskConcurrentDictionary>(() => new TopicTaskConcurrentDictionary());

  private TopicTaskConcurrentDictionary()
  {
    this.underlyingCollection = new ConcurrentDictionary<KeyValuePair<string, string>, IDataPipesService>();
  }

  public void TryAdd(KeyValuePair<string, string> key, IDataPipesService value)
  {
    this.underlyingCollection.TryAdd(key, value);
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new KeyValuePair<KeyValuePair<string, string>, IDataPipesService>(key, value)));
  }

  public IEnumerator<KeyValuePair<KeyValuePair<string, string>, IDataPipesService>> GetEnumerator()
  {
    foreach (KeyValuePair<KeyValuePair<string, string>, IDataPipesService> entry in this.underlyingCollection)
    {
      yield return entry;
    }
  }

  IEnumerator IEnumerable.GetEnumerator()
  {
    return GetEnumerator();
  }

  private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
  {
    this.CollectionChanged?.Invoke(this, e);
  }
}

基本上_instance必须引用底层集合的实例TopicTaskConcurrentDictionary而不是底层集合的实例(如在原始实现中)。

例子:

TopicTaskConcurrentDictionary.Instance.CollectionChanged += delegate (object o, NotifyCollectionChangedEventArgs e)
{
  foreach (KeyValuePair<KeyValuePair<string, string>, IDataPipesService> item in TopicTaskConcurrentDictionary.Instance)
  {
    ;
  }
};

var dataService = _kernel.Get<IDataService>();
TopicTaskConcurrentDictionary.Instance.TryAdd(new KeyValuePair<string, string>(param.TagPrefix, param.TopicName), dataService);

推荐阅读