From ad0c97205f5e09fe52abbac77e9f937cd8f7506c Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 5 Sep 2023 16:31:12 -0400 Subject: [PATCH 1/2] Added CollectionThreadSafe. Observable Objects and Collections now Wait when Calling Events --- README.md | 12 + .../Collections/CollectionThreadSafe.cs | 203 +++++++++++++++ .../Collections/DictionaryThreadSafe.cs | 29 ++- .../Collections/ListThreadSafe.cs | 8 +- .../ObservableCollectionThreadSafe.cs | 240 +++++++----------- .../ObservableDictionaryThreadSafe.cs | 71 +++++- .../Collections/QueueThreadSafe.cs | 4 +- .../Collections/SortedListThreadSafe.cs | 18 +- .../ObservableDataCollection.cs | 8 +- .../ObservableDataDictionary.cs | 19 +- .../DataObjects/BindableDataObject.cs | 19 +- .../INotifyCollectionChangedExtension.cs | 8 +- .../INotifyPropertyChangedExtension.cs | 14 +- .../Interfaces/IBindableCollection.cs | 11 + .../Interfaces/ICollectionThreadSafe.cs | 18 ++ .../IObservableCollectionThreadSafe.cs | 10 +- .../IObservableDictionaryThreadSafe.cs | 2 +- .../Objects/BindableObject.cs | 18 +- .../Objects/ThreadObject.cs | 2 +- 19 files changed, 504 insertions(+), 210 deletions(-) create mode 100644 src/ThunderDesign.Net-PCL.Threading/Collections/CollectionThreadSafe.cs create mode 100644 src/ThunderDesign.Net-PCL.Threading/Interfaces/IBindableCollection.cs create mode 100644 src/ThunderDesign.Net-PCL.Threading/Interfaces/ICollectionThreadSafe.cs diff --git a/README.md b/README.md index d2e6ee9..5582973 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ A simple C# repository containing a few basic useful Thread-Safe Objects. - Collections - ObservableDictionaryThreadSafe - ObservableCollectionThreadSafe + - CollectionThreadSafe - DictionaryThreadSafe - SortedListThreadSafe - ListThreadSafe @@ -56,3 +57,14 @@ Use the `-version` option to specify an [older version](https://www.nuget.org/pa This is an open source project that welcomes contributions/suggestions/bug reports from those who use it. If you have any ideas on how to improve the library, please [post an issue here on GitHub](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.Threading/issues). Please check out the [How to Contribute](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.Threading/blob/main/.github/CONTRIBUTING.md). +---- + +## Breaking changes from v1.0.7 to v1.0.8! + +Observable Objects now Wait when calling `PropertyChanged` Event. +This can be overwritten durring creation or by setting Property `WaitOnNotifyPropertyChanged`. Default value is `true`. + +Observable Collections now Wait when calling `CollectionChanged` Event. +This can be overwritten durring creation or by setting Property `WaitOnNotifyCollectionChanged`. Default value is `true`. + +*(TIP: If you experience Dead Locks change this value to `false`.)* \ No newline at end of file diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/CollectionThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/CollectionThreadSafe.cs new file mode 100644 index 0000000..1ed9143 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/CollectionThreadSafe.cs @@ -0,0 +1,203 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading; +using ThunderDesign.Net.Threading.Interfaces; + +namespace ThunderDesign.Net.Threading.Collections +{ + public class CollectionThreadSafe : Collection, ICollectionThreadSafe + { + #region constructors + public CollectionThreadSafe() : base() { } + + public CollectionThreadSafe(IList list) : base(list) { } + #endregion + + #region properties + public bool IsSynchronized + { + get { return true; } + } + + public new T this[int index] + { + get + { + _ReaderWriterLockSlim.EnterReadLock(); + try + { + return base[index]; + } + finally + { + _ReaderWriterLockSlim.ExitReadLock(); + } + } + set + { + _ReaderWriterLockSlim.EnterWriteLock(); + try + { + base[index] = value; + } + finally + { + _ReaderWriterLockSlim.ExitWriteLock(); + } + } + } + + public new int Count + { + get + { + _ReaderWriterLockSlim.EnterReadLock(); + try + { + return base.Count; + } + finally + { + _ReaderWriterLockSlim.ExitReadLock(); + } + } + } + #endregion + + #region methods + public new void Add(T item) + { + _ReaderWriterLockSlim.EnterWriteLock(); + try + { + base.Add(item); + } + finally + { + _ReaderWriterLockSlim.ExitWriteLock(); + } + } + + public new void Clear() + { + _ReaderWriterLockSlim.EnterWriteLock(); + try + { + base.Clear(); + } + finally + { + _ReaderWriterLockSlim.ExitWriteLock(); + } + } + + public new bool Contains(T item) + { + _ReaderWriterLockSlim.EnterReadLock(); + try + { + return base.Contains(item); + } + finally + { + _ReaderWriterLockSlim.ExitReadLock(); + } + } + + public new void CopyTo(T[] array, int index) + { + _ReaderWriterLockSlim.EnterReadLock(); + try + { + base.CopyTo(array, index); + } + finally + { + _ReaderWriterLockSlim.ExitReadLock(); + } + } + + public new IEnumerator GetEnumerator() + { + _ReaderWriterLockSlim.EnterReadLock(); + try + { + return base.GetEnumerator(); + } + finally + { + _ReaderWriterLockSlim.ExitReadLock(); + } + } + + public new int IndexOf(T item) + { + _ReaderWriterLockSlim.EnterReadLock(); + try + { + return base.IndexOf(item); + } + finally + { + _ReaderWriterLockSlim.ExitReadLock(); + } + } + + public new void Insert(int index, T item) + { + _ReaderWriterLockSlim.EnterWriteLock(); + try + { + base.Insert(index, item); + } + finally + { + _ReaderWriterLockSlim.ExitWriteLock(); + } + } + + public new bool Remove(T item) + { + _ReaderWriterLockSlim.EnterWriteLock(); + try + { + return base.Remove(item); + } + finally + { + _ReaderWriterLockSlim.ExitWriteLock(); + } + } + + public new void RemoveAt(int index) + { + _ReaderWriterLockSlim.EnterWriteLock(); + try + { + base.RemoveAt(index); + } + finally + { + _ReaderWriterLockSlim.ExitWriteLock(); + } + } + + public T GetItemByIndex(int index) + { + _ReaderWriterLockSlim.EnterReadLock(); + try + { + return this[index]; + } + finally + { + _ReaderWriterLockSlim.ExitReadLock(); + } + } + #endregion + + #region variables + protected readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/DictionaryThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/DictionaryThreadSafe.cs index 5f3d8d4..7eee245 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/DictionaryThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/DictionaryThreadSafe.cs @@ -10,16 +10,21 @@ namespace ThunderDesign.Net.Threading.Collections { public class DictionaryThreadSafe : Dictionary, IDictionaryThreadSafe { -#region constructors + #region constructors public DictionaryThreadSafe() : base() { } + public DictionaryThreadSafe(int capacity) : base(capacity) { } + public DictionaryThreadSafe(IEqualityComparer comparer) : base(comparer) { } + public DictionaryThreadSafe(IDictionary dictionary) : base(dictionary) { } + public DictionaryThreadSafe(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } + public DictionaryThreadSafe(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary, comparer) { } -#endregion + #endregion -#region properties + #region properties public bool IsSynchronized { get { return true; } @@ -116,10 +121,10 @@ public bool IsSynchronized } } } -#endregion + #endregion -#region methods - public new virtual void Add(TKey key, TValue value) + #region methods + public new void Add(TKey key, TValue value) { _ReaderWriterLockSlim.EnterWriteLock(); try @@ -132,7 +137,7 @@ public bool IsSynchronized } } - public new virtual void Clear() + public new void Clear() { _ReaderWriterLockSlim.EnterWriteLock(); try @@ -212,7 +217,7 @@ public override void OnDeserialization(Object sender) } } #endif - public new virtual bool Remove(TKey key) + public new bool Remove(TKey key) { bool result = false; _ReaderWriterLockSlim.EnterWriteLock(); @@ -239,10 +244,10 @@ public override void OnDeserialization(Object sender) _ReaderWriterLockSlim.ExitReadLock(); } } -#endregion + #endregion -#region variables - protected static readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); -#endregion + #region variables + protected readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + #endregion } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/ListThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/ListThreadSafe.cs index 960a60e..5cfd793 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/ListThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/ListThreadSafe.cs @@ -699,10 +699,10 @@ public bool IsSynchronized _ReaderWriterLockSlim.ExitWriteLock(); } } -#endregion + #endregion -#region variables - protected static readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); -#endregion + #region variables + protected readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + #endregion } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/ObservableCollectionThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/ObservableCollectionThreadSafe.cs index 4a30c8c..8cac4bc 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/ObservableCollectionThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/ObservableCollectionThreadSafe.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; @@ -8,227 +9,180 @@ namespace ThunderDesign.Net.Threading.Collections { - public class ObservableCollectionThreadSafe : ObservableCollection, IObservableCollectionThreadSafe + public class ObservableCollectionThreadSafe : CollectionThreadSafe, IObservableCollectionThreadSafe { #region constructors - public ObservableCollectionThreadSafe() : base() { } - public ObservableCollectionThreadSafe(IEnumerable collection) : base(collection) { } - public ObservableCollectionThreadSafe(List list) : base(list) { } - #endregion - - #region event handlers - public override event NotifyCollectionChangedEventHandler CollectionChanged; - protected override event PropertyChangedEventHandler PropertyChanged; - #endregion - - #region properties - public bool IsSynchronized + public ObservableCollectionThreadSafe(bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base() { - get { return true; } + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + _WaitOnNotifyCollectionChanged = waitOnNotifyCollectionChanged; } - public new T this[int index] + public ObservableCollectionThreadSafe(List list, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) + : base((list != null) ? new List(list.Count) : list) { - get - { - _ReaderWriterLockSlim.EnterReadLock(); - try - { - return base[index]; - } - finally - { - _ReaderWriterLockSlim.ExitReadLock(); - } - } - set - { - _ReaderWriterLockSlim.EnterWriteLock(); - try - { - base[index] = value; - } - finally - { - _ReaderWriterLockSlim.ExitWriteLock(); - } - } + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + _WaitOnNotifyCollectionChanged = waitOnNotifyCollectionChanged; + // doesn't copy the list (contrary to the documentation) - it uses the + // list directly as its storage. So we do the copying here. + // + CopyFrom(list); } - public new int Count + public ObservableCollectionThreadSafe(IEnumerable collection, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) { - get - { - _ReaderWriterLockSlim.EnterReadLock(); - try - { - return base.Count; - } - finally - { - _ReaderWriterLockSlim.ExitReadLock(); - } - } + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + _WaitOnNotifyCollectionChanged = waitOnNotifyCollectionChanged; + if (collection == null) + throw new ArgumentNullException("collection"); + + CopyFrom(collection); } #endregion - #region methods - public new void Move(int oldIndex, int newIndex) - { - _ReaderWriterLockSlim.EnterWriteLock(); - try - { - base.Move(oldIndex, newIndex); - } - finally - { - _ReaderWriterLockSlim.ExitWriteLock(); - } - } + #region event handlers + public event NotifyCollectionChangedEventHandler CollectionChanged; + public event PropertyChangedEventHandler PropertyChanged; + #endregion - public new virtual void Add(T item) + #region properties + public bool WaitOnNotifyPropertyChanged { - _ReaderWriterLockSlim.EnterWriteLock(); - try - { - base.Add(item); - } - finally - { - _ReaderWriterLockSlim.ExitWriteLock(); - } + get { return this.GetProperty(ref _WaitOnNotifyPropertyChanged, _Locker); } + set { this.SetProperty(ref _WaitOnNotifyPropertyChanged, value, _Locker, true); } } - public new void Clear() + public bool WaitOnNotifyCollectionChanged { - _ReaderWriterLockSlim.EnterWriteLock(); - try - { - base.Clear(); - } - finally - { - _ReaderWriterLockSlim.ExitWriteLock(); - } + get { return this.GetProperty(ref _WaitOnNotifyCollectionChanged, _Locker); } + set { this.SetProperty(ref _WaitOnNotifyCollectionChanged, value, _Locker, true); } } + #endregion - public new bool Contains(T item) + #region methods + private void CopyFrom(IEnumerable collection) { - _ReaderWriterLockSlim.EnterReadLock(); - try - { - return base.Contains(item); - } - finally + IList items = Items; + if (collection != null && items != null) { - _ReaderWriterLockSlim.ExitReadLock(); + using (IEnumerator enumerator = collection.GetEnumerator()) + { + while (enumerator.MoveNext()) + { + items.Add(enumerator.Current); + } + } } } - public new void CopyTo(T[] array, int index) + public void Move(int oldIndex, int newIndex) { + T removedItem = default; _ReaderWriterLockSlim.EnterWriteLock(); try { - base.CopyTo(array, index); + removedItem = this[oldIndex]; + base.RemoveItem(oldIndex); + base.InsertItem(newIndex, removedItem); } finally { _ReaderWriterLockSlim.ExitWriteLock(); } + OnPropertyChanged(IndexerName); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, removedItem, newIndex, oldIndex)); } - public new IEnumerator GetEnumerator() + public new void Add(T item) { - _ReaderWriterLockSlim.EnterReadLock(); - try - { - return base.GetEnumerator(); - } - finally - { - _ReaderWriterLockSlim.ExitReadLock(); - } + base.Add(item); + OnPropertyChanged(nameof(Count)); + OnPropertyChanged(IndexerName); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, IndexOf(item))); } - public new int IndexOf(T item) + public new void Clear() { - _ReaderWriterLockSlim.EnterReadLock(); - try - { - return base.IndexOf(item); - } - finally - { - _ReaderWriterLockSlim.ExitReadLock(); - } + base.Clear(); + OnPropertyChanged(nameof(Count)); + OnPropertyChanged(IndexerName); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public new void Insert(int index, T item) { - _ReaderWriterLockSlim.EnterWriteLock(); - try - { - base.Insert(index, item); - } - finally - { - _ReaderWriterLockSlim.ExitWriteLock(); - } + base.Insert(index, item); + OnPropertyChanged(nameof(Count)); + OnPropertyChanged(IndexerName); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } public new bool Remove(T item) { + var result = false; + int index = -1; + T removedItem = default; _ReaderWriterLockSlim.EnterWriteLock(); try { - return base.Remove(item); + index = Items.IndexOf(item); + if (index >= 0) + removedItem = this[index]; + result = base.Remove(item); } finally { _ReaderWriterLockSlim.ExitWriteLock(); } + if (result) + { + OnPropertyChanged(nameof(Count)); + OnPropertyChanged(IndexerName); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItem, index)); + } + return result; } public new void RemoveAt(int index) { + T removedItem = default; + _ReaderWriterLockSlim.EnterWriteLock(); try { + if (index >= 0 || index < Items.Count) + removedItem = this[index]; + base.RemoveAt(index); } finally { _ReaderWriterLockSlim.ExitWriteLock(); } + OnPropertyChanged(nameof(Count)); + OnPropertyChanged(IndexerName); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItem, index)); } - public T GetItemByIndex(int index) + public void OnPropertyChanged(string propertyName) { - _ReaderWriterLockSlim.EnterReadLock(); - try - { - return this[index]; - } - finally - { - _ReaderWriterLockSlim.ExitReadLock(); - } + this.NotifyPropertyChanged(PropertyChanged, propertyName, WaitOnNotifyPropertyChanged); } - protected override void OnPropertyChanged(PropertyChangedEventArgs e) - { - this.NotifyPropertyChanged(PropertyChanged, e.PropertyName); - } + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - this.NotifyCollectionChanged(CollectionChanged, e); + this.NotifyCollectionChanged(CollectionChanged, args, WaitOnNotifyCollectionChanged); } #endregion #region variables - protected static readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + protected readonly object _Locker = new object(); + protected bool _WaitOnNotifyPropertyChanged = true; + protected bool _WaitOnNotifyCollectionChanged = true; + // This must agree with Binding.IndexerName. It is declared separately + // here so as to avoid a dependency on PresentationFramework.dll. + private const string IndexerName = "Item[]"; #endregion } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/ObservableDictionaryThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/ObservableDictionaryThreadSafe.cs index 4a4f128..4efdfeb 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/ObservableDictionaryThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/ObservableDictionaryThreadSafe.cs @@ -9,12 +9,41 @@ namespace ThunderDesign.Net.Threading.Collections public class ObservableDictionaryThreadSafe : DictionaryThreadSafe, IObservableDictionaryThreadSafe { #region constructors - public ObservableDictionaryThreadSafe() : base() { } - public ObservableDictionaryThreadSafe(int capacity) : base(capacity) { } - public ObservableDictionaryThreadSafe(IEqualityComparer comparer) : base(comparer) { } - public ObservableDictionaryThreadSafe(IDictionary dictionary) : base(dictionary) { } - public ObservableDictionaryThreadSafe(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } - public ObservableDictionaryThreadSafe(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary, comparer) { } + public ObservableDictionaryThreadSafe(bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base() + { + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + _WaitOnNotifyCollectionChanged = waitOnNotifyCollectionChanged; + } + + public ObservableDictionaryThreadSafe(int capacity, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(capacity) + { + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + _WaitOnNotifyCollectionChanged = waitOnNotifyCollectionChanged; + } + + public ObservableDictionaryThreadSafe(IEqualityComparer comparer, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(comparer) + { + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + _WaitOnNotifyCollectionChanged = waitOnNotifyCollectionChanged; + } + + public ObservableDictionaryThreadSafe(IDictionary dictionary, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(dictionary) + { + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + _WaitOnNotifyCollectionChanged = waitOnNotifyCollectionChanged; + } + + public ObservableDictionaryThreadSafe(int capacity, IEqualityComparer comparer, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(capacity, comparer) + { + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + _WaitOnNotifyCollectionChanged = waitOnNotifyCollectionChanged; + } + + public ObservableDictionaryThreadSafe(IDictionary dictionary, IEqualityComparer comparer, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(dictionary, comparer) + { + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + _WaitOnNotifyCollectionChanged = waitOnNotifyCollectionChanged; + } #endregion #region event handlers @@ -47,10 +76,22 @@ public ObservableDictionaryThreadSafe(IDictionary dictionary, IEqu OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, originalValue)); } } + + public bool WaitOnNotifyPropertyChanged + { + get { return this.GetProperty(ref _WaitOnNotifyPropertyChanged, _Locker); } + set { this.SetProperty(ref _WaitOnNotifyPropertyChanged, value, _Locker, true); } + } + + public bool WaitOnNotifyCollectionChanged + { + get { return this.GetProperty(ref _WaitOnNotifyCollectionChanged, _Locker); } + set { this.SetProperty(ref _WaitOnNotifyCollectionChanged, value, _Locker, true); } + } #endregion #region methods - public override void Add(TKey key, TValue value) + public new void Add(TKey key, TValue value) { base.Add(key, value); OnPropertyChanged(nameof(Keys)); @@ -59,7 +100,7 @@ public override void Add(TKey key, TValue value) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value)); } - public override void Clear() + public new void Clear() { base.Clear(); OnPropertyChanged(nameof(Keys)); @@ -68,7 +109,7 @@ public override void Clear() OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - public override bool Remove(TKey key) + public new bool Remove(TKey key) { bool result = false; TValue value; @@ -92,15 +133,21 @@ public override bool Remove(TKey key) return result; } - protected virtual void OnPropertyChanged(string propertyName) + public void OnPropertyChanged(string propertyName) { - this.NotifyPropertyChanged(PropertyChanged, propertyName); + this.NotifyPropertyChanged(PropertyChanged, propertyName, WaitOnNotifyPropertyChanged); } protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { - this.NotifyCollectionChanged(CollectionChanged, args); + this.NotifyCollectionChanged(CollectionChanged, args, WaitOnNotifyCollectionChanged); } #endregion + + #region variables + protected readonly object _Locker = new object(); + protected bool _WaitOnNotifyPropertyChanged = true; + protected bool _WaitOnNotifyCollectionChanged = true; + #endregion } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/QueueThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/QueueThreadSafe.cs index 1cbcb87..113f4e5 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/QueueThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/QueueThreadSafe.cs @@ -8,7 +8,9 @@ public class QueueThreadSafe : Queue, IQueueThreadSafe { #region constructors public QueueThreadSafe() : base() { } + public QueueThreadSafe(IEnumerable collection) : base(collection) { } + public QueueThreadSafe(int capacity) : base(capacity) { } #endregion @@ -155,7 +157,7 @@ public bool IsSynchronized #endregion #region variables - protected static readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + protected readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); #endregion } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/SortedListThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/SortedListThreadSafe.cs index 0a068a0..44407e5 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/SortedListThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/SortedListThreadSafe.cs @@ -7,7 +7,7 @@ namespace ThunderDesign.Net_PCL.Threading.Collections #if NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 || NETSTANDARD2_0 || NETSTANDARD2_1 public class SortedListThreadSafe : SortedList, ISortedListThreadSafe { -#region constructors + #region constructors public SortedListThreadSafe() : base() { } public SortedListThreadSafe(IComparer comparer) : base(comparer) { } @@ -19,9 +19,9 @@ public SortedListThreadSafe(IDictionary dictionary, IComparer comparer) : base(capacity, comparer) { } -#endregion + #endregion -#region properties + #region properties public bool IsSynchronized { get { return true; } @@ -146,9 +146,9 @@ public bool IsSynchronized } } } -#endregion + #endregion -#region methods + #region methods public new void Add(TKey key, TValue value) { _ReaderWriterLockSlim.EnterWriteLock(); @@ -292,11 +292,11 @@ public bool IsSynchronized _ReaderWriterLockSlim.ExitReadLock(); } } -#endregion + #endregion -#region variables - protected static readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); -#endregion + #region variables + protected readonly ReaderWriterLockSlim _ReaderWriterLockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + #endregion } #endif } diff --git a/src/ThunderDesign.Net-PCL.Threading/DataCollections/ObservableDataCollection.cs b/src/ThunderDesign.Net-PCL.Threading/DataCollections/ObservableDataCollection.cs index 0a7518c..aa98e6c 100644 --- a/src/ThunderDesign.Net-PCL.Threading/DataCollections/ObservableDataCollection.cs +++ b/src/ThunderDesign.Net-PCL.Threading/DataCollections/ObservableDataCollection.cs @@ -7,9 +7,11 @@ namespace ThunderDesign.Net.Threading.DataCollections public class ObservableDataCollection : ObservableCollectionThreadSafe, IObservableDataCollection where T : IBindableDataObject { #region constructors - public ObservableDataCollection() : base() { } - public ObservableDataCollection(IEnumerable collection) : base(collection) { } - public ObservableDataCollection(List list) : base(list) { } + public ObservableDataCollection(bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(waitOnNotifyPropertyChanged, waitOnNotifyCollectionChanged) { } + + public ObservableDataCollection(IEnumerable collection, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(collection, waitOnNotifyPropertyChanged, waitOnNotifyCollectionChanged) { } + + public ObservableDataCollection(List list, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(list, waitOnNotifyPropertyChanged, waitOnNotifyCollectionChanged) { } #endregion } } diff --git a/src/ThunderDesign.Net-PCL.Threading/DataCollections/ObservableDataDictionary.cs b/src/ThunderDesign.Net-PCL.Threading/DataCollections/ObservableDataDictionary.cs index cd2a852..69fe472 100644 --- a/src/ThunderDesign.Net-PCL.Threading/DataCollections/ObservableDataDictionary.cs +++ b/src/ThunderDesign.Net-PCL.Threading/DataCollections/ObservableDataDictionary.cs @@ -8,12 +8,17 @@ namespace ThunderDesign.Net.Threading.DataCollections public class ObservableDataDictionary : ObservableDictionaryThreadSafe, IObservableDataDictionary where TValue : IBindableDataObject { #region constructors - public ObservableDataDictionary() : base() { } - public ObservableDataDictionary(int capacity) : base(capacity) { } - public ObservableDataDictionary(IEqualityComparer comparer) : base(comparer) { } - public ObservableDataDictionary(IDictionary dictionary) : base(dictionary) { } - public ObservableDataDictionary(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } - public ObservableDataDictionary(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary, comparer) { } + public ObservableDataDictionary(bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(waitOnNotifyPropertyChanged, waitOnNotifyCollectionChanged) { } + + public ObservableDataDictionary(int capacity, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(capacity, waitOnNotifyPropertyChanged, waitOnNotifyCollectionChanged) { } + + public ObservableDataDictionary(IEqualityComparer comparer, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(comparer, waitOnNotifyPropertyChanged, waitOnNotifyCollectionChanged) { } + + public ObservableDataDictionary(IDictionary dictionary, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(dictionary, waitOnNotifyPropertyChanged, waitOnNotifyCollectionChanged) { } + + public ObservableDataDictionary(int capacity, IEqualityComparer comparer, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(capacity, comparer, waitOnNotifyPropertyChanged, waitOnNotifyCollectionChanged) { } + + public ObservableDataDictionary(IDictionary dictionary, IEqualityComparer comparer, bool waitOnNotifyPropertyChanged = true, bool waitOnNotifyCollectionChanged = true) : base(dictionary, comparer, waitOnNotifyPropertyChanged, waitOnNotifyCollectionChanged) { } #endregion #region methods @@ -29,7 +34,7 @@ void IDictionary.Add(TKey key, TValue value) this.Add(value); } - public virtual void Add(TValue value) + public void Add(TValue value) { base.Add(value.Id, value); } diff --git a/src/ThunderDesign.Net-PCL.Threading/DataObjects/BindableDataObject.cs b/src/ThunderDesign.Net-PCL.Threading/DataObjects/BindableDataObject.cs index aed6498..07a2033 100644 --- a/src/ThunderDesign.Net-PCL.Threading/DataObjects/BindableDataObject.cs +++ b/src/ThunderDesign.Net-PCL.Threading/DataObjects/BindableDataObject.cs @@ -7,12 +7,23 @@ namespace ThunderDesign.Net.Threading.DataObjects { public class BindableDataObject : DataObject, IBindableDataObject { + #region constructors + public BindableDataObject(bool waitOnNotifyPropertyChanged = true) : base() + { + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + } + #endregion + #region event handlers public event PropertyChangedEventHandler PropertyChanged; #endregion #region properties - protected virtual bool WaitWhenNotifying => false; + public bool WaitOnNotifyPropertyChanged + { + get { return this.GetProperty(ref _WaitOnNotifyPropertyChanged, _Locker); } + set { this.SetProperty(ref _WaitOnNotifyPropertyChanged, value, _Locker, true); } + } #endregion #region methods @@ -23,8 +34,12 @@ protected override void SetId(Key value) public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") { - this.NotifyPropertyChanged(PropertyChanged, propertyName, WaitWhenNotifying); + this.NotifyPropertyChanged(PropertyChanged, propertyName, WaitOnNotifyPropertyChanged); } #endregion + + #region variables + protected bool _WaitOnNotifyPropertyChanged = true; + #endregion } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Extentions/INotifyCollectionChangedExtension.cs b/src/ThunderDesign.Net-PCL.Threading/Extentions/INotifyCollectionChangedExtension.cs index 76c039a..b806387 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Extentions/INotifyCollectionChangedExtension.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Extentions/INotifyCollectionChangedExtension.cs @@ -8,12 +8,16 @@ public static class INotifyCollectionChangedExtension public static void NotifyCollectionChanged( this INotifyCollectionChanged sender, NotifyCollectionChangedEventHandler handler, - NotifyCollectionChangedEventArgs args) + NotifyCollectionChangedEventArgs args, + bool notifyAndWait = true) { // Calling 'Invoke' can cause DeadLocks and 'BeginInvoke' can cause System.PlatformNotSupportedException errors so calling Invoke from within a Thread //handler?.Invoke(sender, args); //handler?.BeginInvoke(sender, args, ar => { }, null); - ThreadHelper.RunAndForget(() => handler?.Invoke(sender, args)); + if (notifyAndWait) + handler?.Invoke(sender, args); + else + ThreadHelper.RunAndForget(() => handler?.Invoke(sender, args)); } } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Extentions/INotifyPropertyChangedExtension.cs b/src/ThunderDesign.Net-PCL.Threading/Extentions/INotifyPropertyChangedExtension.cs index 9ecaeea..ae88cdb 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Extentions/INotifyPropertyChangedExtension.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Extentions/INotifyPropertyChangedExtension.cs @@ -11,7 +11,7 @@ public static void NotifyPropertyChanged( this INotifyPropertyChanged sender, PropertyChangedEventHandler handler, [CallerMemberName] string propertyName = "", - bool notifyAndWait = false) + bool notifyAndWait = true) { sender.NotifyPropertyChanged(handler, new PropertyChangedEventArgs(propertyName), notifyAndWait); } @@ -20,7 +20,7 @@ public static void NotifyPropertyChanged( this INotifyPropertyChanged sender, PropertyChangedEventHandler handler, PropertyChangedEventArgs args, - bool notifyAndWait = false) + bool notifyAndWait = true) { // Calling 'Invoke' can cause DeadLocks and 'BeginInvoke' can cause System.PlatformNotSupportedException errors so calling Invoke from within a Thread //handler?.Invoke(sender, args); @@ -36,9 +36,10 @@ public static bool SetProperty( ref T backingStore, T value, PropertyChangedEventHandler propertyChangedEventHandler, - [CallerMemberName] string propertyName = "") + [CallerMemberName] string propertyName = "", + bool notifyAndWait = true) { - return sender.SetProperty(ref backingStore, value, null, propertyChangedEventHandler, propertyName); + return sender.SetProperty(ref backingStore, value, null, propertyChangedEventHandler, propertyName, notifyAndWait); } public static bool SetProperty( @@ -47,12 +48,13 @@ public static bool SetProperty( T value, object lockObj, PropertyChangedEventHandler propertyChangedEventHandler, - [CallerMemberName] string propertyName = "") + [CallerMemberName] string propertyName = "", + bool notifyAndWait = true) { if (sender.SetProperty(ref backingStore, value, lockObj)) { - sender.NotifyPropertyChanged(propertyChangedEventHandler, propertyName); + sender.NotifyPropertyChanged(propertyChangedEventHandler, propertyName, notifyAndWait); return true; } else diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IBindableCollection.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IBindableCollection.cs new file mode 100644 index 0000000..e0a53fc --- /dev/null +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IBindableCollection.cs @@ -0,0 +1,11 @@ +using System.Collections.Specialized; + +namespace ThunderDesign.Net.Threading.Interfaces +{ + public interface IBindableCollection : IBindableObject, INotifyCollectionChanged + { + #region methods + //void OnCollectionChanged(NotifyCollectionChangedEventArgs args); + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/ICollectionThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/ICollectionThreadSafe.cs new file mode 100644 index 0000000..fe9b2a3 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/ICollectionThreadSafe.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace ThunderDesign.Net.Threading.Interfaces +{ + public interface ICollectionThreadSafe : IList, ICollection, IEnumerable + { + } + + public interface ICollectionThreadSafe : IList, ICollection, IReadOnlyList, IReadOnlyCollection, IEnumerable, ICollectionThreadSafe + { + #region methods + T GetItemByIndex(int index); + #endregion + } +} diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IObservableCollectionThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IObservableCollectionThreadSafe.cs index d5677f9..6540cd3 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IObservableCollectionThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IObservableCollectionThreadSafe.cs @@ -5,14 +5,14 @@ namespace ThunderDesign.Net.Threading.Interfaces { - public interface IObservableCollectionThreadSafe : IList, INotifyCollectionChanged, INotifyPropertyChanged + public interface IObservableCollectionThreadSafe : ICollectionThreadSafe, IBindableCollection { + #region methods + void Move(int oldIndex, int newIndex); + #endregion } - public interface IObservableCollectionThreadSafe : IList, IObservableCollectionThreadSafe + public interface IObservableCollectionThreadSafe : ICollectionThreadSafe, IObservableCollectionThreadSafe { - #region methods - T GetItemByIndex(int index); - #endregion } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IObservableDictionaryThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IObservableDictionaryThreadSafe.cs index 98b44db..27891d8 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IObservableDictionaryThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IObservableDictionaryThreadSafe.cs @@ -3,7 +3,7 @@ namespace ThunderDesign.Net.Threading.Interfaces { - public interface IObservableDictionaryThreadSafe : IDictionaryThreadSafe, INotifyCollectionChanged, INotifyPropertyChanged + public interface IObservableDictionaryThreadSafe : IDictionaryThreadSafe, IBindableCollection { } diff --git a/src/ThunderDesign.Net-PCL.Threading/Objects/BindableObject.cs b/src/ThunderDesign.Net-PCL.Threading/Objects/BindableObject.cs index 6361e9b..3efc5d6 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Objects/BindableObject.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Objects/BindableObject.cs @@ -10,19 +10,33 @@ namespace ThunderDesign.Net.Threading.Objects { public class BindableObject : ThreadObject, IBindableObject { + #region constructors + public BindableObject(bool waitOnNotifyPropertyChanged = true) : base() + { + _WaitOnNotifyPropertyChanged = waitOnNotifyPropertyChanged; + } + #endregion #region event handlers public event PropertyChangedEventHandler PropertyChanged; #endregion #region properties - protected virtual bool WaitWhenNotifying => false; + public bool WaitOnNotifyPropertyChanged + { + get { return this.GetProperty(ref _WaitOnNotifyPropertyChanged, _Locker); } + set { this.SetProperty(ref _WaitOnNotifyPropertyChanged, value, _Locker, true); } + } #endregion #region methods public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") { - this.NotifyPropertyChanged(PropertyChanged, propertyName, WaitWhenNotifying); + this.NotifyPropertyChanged(PropertyChanged, propertyName, WaitOnNotifyPropertyChanged); } #endregion + + #region variables + protected bool _WaitOnNotifyPropertyChanged = true; + #endregion } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Objects/ThreadObject.cs b/src/ThunderDesign.Net-PCL.Threading/Objects/ThreadObject.cs index 9a3029a..131001d 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Objects/ThreadObject.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Objects/ThreadObject.cs @@ -3,7 +3,7 @@ public class ThreadObject { #region variables - protected readonly static object _Locker = new object(); + protected readonly object _Locker = new object(); #endregion } } From 2bbcfc4295061134d4bedd8313da0233fa0a569e Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 5 Sep 2023 16:33:13 -0400 Subject: [PATCH 2/2] Updated Sample to work with v1.0.8 changes --- .../SimpleContacts/SimpleContacts/Models/ContactsModelList.cs | 4 ++-- .../SimpleContacts/SimpleContacts/Views/ContactsView.xaml | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/samples/Xamarin/SimpleContacts/SimpleContacts/Models/ContactsModelList.cs b/samples/Xamarin/SimpleContacts/SimpleContacts/Models/ContactsModelList.cs index b7c26ad..e192840 100644 --- a/samples/Xamarin/SimpleContacts/SimpleContacts/Models/ContactsModelList.cs +++ b/samples/Xamarin/SimpleContacts/SimpleContacts/Models/ContactsModelList.cs @@ -12,7 +12,7 @@ public static ContactsModelList Instance { get { - lock (_Locker) + lock (_InstanceLocker) { return _Instance ?? (_Instance= new ContactsModelList()); } @@ -21,7 +21,7 @@ public static ContactsModelList Instance #endregion #region variables - protected readonly static object _Locker = new object(); + protected readonly static object _InstanceLocker = new object(); private static ContactsModelList _Instance = null; #endregion } diff --git a/samples/Xamarin/SimpleContacts/SimpleContacts/Views/ContactsView.xaml b/samples/Xamarin/SimpleContacts/SimpleContacts/Views/ContactsView.xaml index 4a1175b..c2f679d 100644 --- a/samples/Xamarin/SimpleContacts/SimpleContacts/Views/ContactsView.xaml +++ b/samples/Xamarin/SimpleContacts/SimpleContacts/Views/ContactsView.xaml @@ -3,13 +3,11 @@ xmlns:b="clr-namespace:SimpleContacts.Views.Base" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" - xmlns:f="clr-namespace:SimpleContacts.Fonts" Title="Contacts" x:Class="SimpleContacts.Views.ContactsView" x:TypeArguments="vm:ContactsViewModel" xmlns:v="clr-namespace:SimpleContacts.Views" xmlns:vm="clr-namespace:SimpleContacts.ViewModels" - xmlns:c="clr-namespace:SimpleContacts.Controls" xmlns:fab="clr-namespace:ThunderDesign.Xamarin.Forms.FloatingActionButton.Controls;assembly=ThunderDesign.Xamarin.Forms.FloatingActionButton">