C#: Как создать высокопроизводительный асинхронный словарь с отслеживанием обновлений в реальном времени в .NET 8C#

Место общения программистов C#
Ответить
Anonymous
 C#: Как создать высокопроизводительный асинхронный словарь с отслеживанием обновлений в реальном времени в .NET 8

Сообщение Anonymous »

Мне нужно реализовать относительно простой класс словаря на C# с асинхронным методом WaitForUpdate. Этот метод должен позволять асинхронно ожидать обновления значения указанного ключа. По умолчанию обновление происходит, когда для ссылочных типов (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 CollectionChanged;
}

///
/// Represents a thread-safe collection of key/value pairs that allows awaiting
/// notifications for value updates.
///
/// The type of the keys in the dictionary
/// The type of the values in the dictionary
class AwaitableDictionary : IEnumerable {

private class DefaultEqualityComparer : IEqualityComparer {

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

private DefaultEqualityComparer() { }

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

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

private class CollectionChangedHandlerController {

private EventHandler EventHandler;

public IObservableCollection Collection { get; private set; }

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

public void AddHandler(EventHandler 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 InnerDictionary;
private readonly ConcurrentDictionary InnerCollectionsDictionary;
private readonly ConcurrentDictionary Awaiters = new();
private readonly IEqualityComparer ValueEqualityComparer = DefaultEqualityComparer.Instance;

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

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

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

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

///
/// Initializes a new instance of the
/// class that is empty.
///
///
/// Specifies the tracked types of changes occurring in the collection.
///
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();
}

///
/// Initializes a new instance of the
/// class that contains elements copied from the specified object.
///
///
/// The whose elements are copied to the new
/// .
///
///
/// 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
/// is used.
///
public AwaitableDictionary(IDictionary dictionary, IEqualityComparer valueEqualityComparer = null) : this() {

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

InnerDictionary = new ConcurrentDictionary(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();
}

///
/// 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 implements ,
/// only changes to the collection's contents are tracked.
///
public async ValueTask 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 GetEnumerator() {

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

return InnerDictionary.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}


Подробнее здесь: https://stackoverflow.com/questions/791 ... e-update-t
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»