C# 如何在 .NET 8 中构建具有实时更新跟踪的高性能异步字典

问题描述 投票:0回答:1

我需要使用异步

WaitForUpdate
方法在 C# 中实现一个相对简单的字典类。此方法应该允许异步等待指定键值的更新。默认情况下,当为引用类型 (TValue) 传递新引用或为值类型分配新值时,会发生更新。此外,如果集合实现了
IObservableCollection
接口,该方法应该监视集合内的更改。

如果添加或更新了值,则

WaitForUpdate
方法应立即返回。鉴于该类将在项目中大量使用,您对改进和优化该类有何建议?

理想情况下,我想避免使用

CollectionChangedHandlerController
并创建一个单独的字典来管理它。但是,我不确定如何在没有它的情况下有效控制事件处理程序(例如,防止触发删除或替换的集合的处理程序)。任何指导将不胜感激!

enum CollectionChangedAction {
    ItemAdded, ItemRemoved, ItemUpdated, Reset
}

class CollectionChangedEventArgs : EventArgs {
    public CollectionChangedAction Action { get; private set; }
    public CollectionChangedEventArgs(CollectionChangedAction action) => Action = action;
}

interface IObservableCollection {
    public event EventHandler<CollectionChangedEventArgs> CollectionChanged;
}

/// <summary>
/// Represents a thread-safe collection of key/value pairs that allows awaiting 
/// notifications for value updates.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary</typeparam>
class AwaitableDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> {

    private class DefaultEqualityComparer<T> : IEqualityComparer<T> {

        public static DefaultEqualityComparer<T> Instance { get; } = new DefaultEqualityComparer<T>();

        private DefaultEqualityComparer() { }

        public bool Equals(T x, T y) {
            if (default(T) == null) return ReferenceEquals(x, y);
            return EqualityComparer<T>.Default.Equals(x, y);
        }

        public int GetHashCode(T obj) {
            if (default(T) == null)
                return obj != null ? RuntimeHelpers.GetHashCode(obj) : 0;
            return EqualityComparer<T>.Default.GetHashCode(obj);
        }
    }

    private class CollectionChangedHandlerController {

        private EventHandler<CollectionChangedEventArgs> EventHandler;

        public IObservableCollection Collection { get; private set; }

        public CollectionChangedHandlerController(IObservableCollection collection) {
            Collection = collection;
        }

        public void AddHandler(EventHandler<CollectionChangedEventArgs> eventHandler) {

            if (eventHandler == null)
                throw new ArgumentNullException(nameof(eventHandler));
            if (EventHandler != null)
                throw new InvalidOperationException("A handler is already registered for this collection change event");

            if (Collection != null) {
                EventHandler = eventHandler;
                Collection.CollectionChanged += eventHandler;
            }
        }

        public void RemoveHandler() {
            if (EventHandler != null) {
                Collection.CollectionChanged -= EventHandler;
                EventHandler = null;
            }
        }
    }

    private readonly bool IsObservableCollectionInValue;
    private readonly ConcurrentDictionary<TKey, TValue> InnerDictionary;
    private readonly ConcurrentDictionary<TKey, CollectionChangedHandlerController> InnerCollectionsDictionary;
    private readonly ConcurrentDictionary<TKey, TaskCompletionSource> Awaiters = new();
    private readonly IEqualityComparer<TValue> ValueEqualityComparer = DefaultEqualityComparer<TValue>.Instance;

    public int Count => IsObservableCollectionInValue ? InnerCollectionsDictionary.Count : InnerDictionary.Count;

    public bool IsEmpty => IsObservableCollectionInValue ? InnerCollectionsDictionary.IsEmpty : InnerDictionary.IsEmpty;

    public CollectionChangedAction[] TrackedChangeActions { get; private set; } 

    /// <summary>
    /// Initializes a new instance of the <see cref="AwaitableDictionary{TKey, TValue}"/>
    /// class that is empty.
    /// </summary>
    public AwaitableDictionary() {
        IsObservableCollectionInValue = typeof(TValue).IsAssignableTo(typeof(IObservableCollection));
        if (!IsObservableCollectionInValue) InnerDictionary = new();
        else {
            InnerCollectionsDictionary = new();
            TrackedChangeActions = [
                CollectionChangedAction.ItemAdded,
                CollectionChangedAction.ItemRemoved,
                CollectionChangedAction.ItemUpdated,
                CollectionChangedAction.Reset];
        }
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="AwaitableDictionary{TKey, TValue}"/>
    /// class that is empty.
    /// </summary>
    /// <param name="trackedChangeActions">
    /// Specifies the tracked types of changes occurring in the collection.
    /// </param>
    public AwaitableDictionary(CollectionChangedAction[] trackedChangeActions) : this() {
        if (trackedChangeActions == null)
            throw new ArgumentNullException(nameof(trackedChangeActions));
        if (!IsObservableCollectionInValue)
            throw new NotSupportedException("Tracking change actions is only" +
                " supported when TValue implements IObservableCollection");
        TrackedChangeActions = trackedChangeActions.Distinct().ToArray();
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="AwaitableDictionary{TKey, TValue}"/>
    /// class that contains elements copied from the specified <see cref="IDictionary{TKey, TValue}"/> object.
    /// </summary>
    /// <param name="dictionary">
    /// The <see cref="IDictionary{TKey, TValue}"/> whose elements are copied to the new 
    /// <see cref="AwaitableDictionary{TKey, TValue}"/>.
    /// </param>
    /// <param name="valueEqualityComparer">
    /// An optional equality comparer used to compare values and determine whether an update notification 
    /// should be triggered when a value is added or updated. If not specified, the default equality comparer for 
    /// <typeparamref name="TValue"/> is used.
    /// </param>
    public AwaitableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TValue> valueEqualityComparer = null) : this() {

        if (dictionary == null)
            throw new ArgumentNullException(nameof(dictionary));

        InnerDictionary = new ConcurrentDictionary<TKey, TValue>(dictionary);
        foreach (var key in dictionary.Keys) {
            var completionSource = new TaskCompletionSource();
            completionSource.SetResult();
            Awaiters.TryAdd(key, completionSource);
        }

        if (valueEqualityComparer != null)
            ValueEqualityComparer = valueEqualityComparer;
    }

    public void AddOrUpdate(TKey key, TValue value) {

        if (IsObservableCollectionInValue) {
            void onCollectionChanged(object sender, CollectionChangedEventArgs e) {
                if (TrackedChangeActions.Contains(e.Action) && 
                    Awaiters.TryGetValue(key, out var completionSource) && 
                    !completionSource.Task.IsCompleted) {

                    completionSource.SetResult();
                }
            };

            CollectionChangedHandlerController newValue = new((IObservableCollection)value);
            newValue.AddHandler(onCollectionChanged);
            InnerCollectionsDictionary.AddOrUpdate(key, newValue, (key, existingValue) => {
                existingValue.RemoveHandler();
                return newValue;
            });
        }
        else {
            bool newValueProduced = true;
            InnerDictionary.AddOrUpdate(key, value, (key, existingValue) => {
                newValueProduced = !ValueEqualityComparer.Equals(value, existingValue);
                return value;
            });

            if (newValueProduced) 
                Awaiters.GetOrAdd(key, _ => new TaskCompletionSource()).TrySetResult();
        }
    }

    public bool TryGetValue(TKey key, out TValue value) {

        if (IsObservableCollectionInValue) {
            if (InnerCollectionsDictionary.TryGetValue(key, out var handlerController)) {
                value = (TValue)handlerController.Collection;
                return true;
            }
            value = default;
            return false;
        }
        else {
            return InnerDictionary.TryGetValue(key, out value);
        }
    }

    public bool TryRemove(TKey key) {

        if (IsObservableCollectionInValue) {
            if (!InnerCollectionsDictionary.TryRemove(key, out var handlerController))
                return false;
            handlerController.RemoveHandler();
        }
        else if (!InnerDictionary.TryRemove(key, out _)) {
            return false;
        }

        if (Awaiters.TryRemove(key, out var completionSource))
            completionSource.TrySetCanceled();
        return true;
    }

    public void Clear() {

        if (IsObservableCollectionInValue) {
            var handlerControllers = InnerCollectionsDictionary.Values.ToArray();
            InnerCollectionsDictionary.Clear();
            foreach (var handlerController in handlerControllers)
                handlerController.RemoveHandler();
        }
        else {
            InnerDictionary.Clear();
        }

        var completionSources = Awaiters.Values.ToArray();
        Awaiters.Clear();
        foreach (var completionSource in completionSources)
            completionSource.TrySetCanceled();
    }

    /// <summary>
    /// Asynchronously waits for an update to the specified key's value.
    /// If the value has already been added or modified, the method returns immediately, 
    /// after which it can be called again to track further changes to the value. 
    /// If <typeparamref name="TValue"/> implements <see cref="IObservableCollection"/>, 
    /// only changes to the collection's contents are tracked.
    /// </summary>
    public async ValueTask<TValue> WaitForUpdate(TKey key, CancellationToken cancellationToken) {
        
        var completionSource = Awaiters.GetOrAdd(key, _ => new TaskCompletionSource());
        await completionSource.Task.WaitAsync(cancellationToken);

        Awaiters.TryUpdate(key, new TaskCompletionSource(), completionSource);

        TValue value;
        if (IsObservableCollectionInValue) {
            if (!InnerCollectionsDictionary.TryGetValue(key, out var handlerController))
                throw new OperationCanceledException();
            value = (TValue)handlerController.Collection;
        }
        else {
            if (!InnerDictionary.TryGetValue(key, out value))
                throw new OperationCanceledException();
        }

        return value;
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {

        if (IsObservableCollectionInValue) {
            return InnerCollectionsDictionary.ToDictionary(pair => pair.Key, 
                pair => (TValue)pair.Value.Collection).GetEnumerator();
        }

        return InnerDictionary.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
c# .net
1个回答
0
投票

快速浏览一下,我有一些建议可以在代码中重复使用,因为您正在使用 .NET 8

CollectionChangedHandlerController 类

我看到您对 EventHandler 进行了空检查:

if (EventHandler != null)
throw new InvalidOperationException("A handler is already registered for this collection change event");

这意味着EventHandler是可为空的,因此您可以在属性上添加可为空的:

private Event<Handler>CollectionChangedEventArgs>? EventHandler;

可选更改:

  • 在只有一个构造函数的类上使用主构造函数
    private class CollectionChangedHandlerController(IObservableCollection collection)
{

    public IObservableCollection Collection { get; } = collection;
}
  • 调整 if null 异常: ArgumentNullException.ThrowIfNull(eventHandler);
© www.soinside.com 2019 - 2024. All rights reserved.