using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; namespace Sicentury.Core.Collections { /// /// The collection is thread-safe. /// /// public class DelayedPresentRollingObservableCollection : IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged { #region Variables public event NotifyCollectionChangedEventHandler CollectionChanged; private readonly List _list; private readonly List _cache; private readonly object _lockList; private readonly object _lockCache; private readonly SemaphoreSlim _semListEnumLocker; private readonly int _presentDelayMillisec = 200; // the default delay is 200ms #endregion #region Constructors public DelayedPresentRollingObservableCollection(int capacity) { if (capacity <= 0) throw new ArgumentException("the capacity must be greate then 0.", nameof(capacity)); _lockList = new object(); _lockCache = new object(); _semListEnumLocker = new SemaphoreSlim(0, 1); Capacity = capacity; lock (_lockList) { _list = new List(); } lock (_lockCache) { _cache = new List(); } _semListEnumLocker.Release(); _ = Task.Run(async () => { Thread.Sleep(2000); while (true) { try { Present(); } catch (Exception e) { Trace.WriteLine($"Unable to present the items, {e.Message}"); Debugger.Break(); } finally { // present the data every 200ms await Task.Delay(_presentDelayMillisec); } } }); } public DelayedPresentRollingObservableCollection(int capacity, int presentDelayMillisec) : this(capacity) { _presentDelayMillisec = presentDelayMillisec < 50 ? 50 : presentDelayMillisec; } #endregion #region Properties public int Capacity { get; } public int Count { get { lock (_lockList) { return _list?.Count ?? 0; } } } #endregion #region Methods /// /// Copy the items from the cache to the presented list and update the UI. /// private void Present() { var itemsChanged = new List(); lock (_lockCache) { if (_cache.Count <= 0) return; itemsChanged.AddRange(_cache); _cache.Clear(); } var notifyAction = NotifyCollectionChangedAction.Add; lock (_lockList) { _list.AddRange(itemsChanged); if (_list.Count >= Capacity) { var cntToRemain = Capacity / 2; _list.RemoveRange(0, _list.Count - cntToRemain); notifyAction = NotifyCollectionChangedAction.Reset; } } // raise the collection changed event. SynchronizationContext.SetSynchronizationContext( new DispatcherSynchronizationContext(Application.Current.Dispatcher)); SynchronizationContext.Current.Post(p1 => { CollectionChanged?.Invoke( this, new NotifyCollectionChangedEventArgs(notifyAction, notifyAction == NotifyCollectionChangedAction.Add ? itemsChanged : null)); }, null); SynchronizationContext.Current.Post(p1 => { OnPropertyChanged(nameof(Count)); }, null); } public void Flush() { Present(); // raise the collection changed event. SynchronizationContext.SetSynchronizationContext( new DispatcherSynchronizationContext(Application.Current.Dispatcher)); SynchronizationContext.Current.Post(p1 => { CollectionChanged?.Invoke( this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); }, null); SynchronizationContext.Current.Post(p1 => { OnPropertyChanged(nameof(Count)); }, null); } public void Add(T newItem) { lock (_lockCache) { _cache.Add(newItem); } } public bool Remove(T oldItem) { lock (_lockList) { return _list.Remove(oldItem); } } public void RemoveAt(int index) { lock (_lockList) { _list.RemoveAt(index); } } public void RemoveRange(int index, int count) { lock (_lockList) { _list.RemoveRange(index, count); } } public void Clear() { lock (_lockCache) { _cache.Clear(); } lock (_lockList) { _list.Clear(); } } public IEnumerator GetEnumerator() { lock (_lockList) { _semListEnumLocker.Wait(); return new SafeEnumerator(_list.GetEnumerator(), _semListEnumLocker); } } IEnumerator IEnumerable.GetEnumerator() { lock (_lockList) { _semListEnumLocker.Wait(); return new SafeEnumerator(_list.GetEnumerator(), _semListEnumLocker); } } public T this[int index] { get { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(); lock (_lockList) { return _list[index]; } } } #endregion public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }