无法在 C# ConcurrentDictionary 上调用 PropertyChanged 事件

Unable to invoke PropertyChanged event on a C# ConcurrentDictionary

我正在尝试在 C# 中使用 ConcurrentDictionary 并在新项目添加到字典时处理事件,但我无法这样做,代码如下

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 事件从未被调用,请问我哪里出错了?

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

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

我将 PropertyChanged 替换为 CollectionChanged(这在绑定场景中启用了 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);