Una raccolta osservabile "ThreadSafe" - C #
Poiché in .net non esiste una raccolta simultanea che consente di rimuovere elementi specifici, ho messo insieme la seguente classe.
È importante notare che è sicuro per i thread solo fino alla LockTimeoutscadenza di una determinata operazione.
L'obiettivo principale era quello di proteggersi dalle insolite eccezioni "InvalidOperationException: Collection Was Modified", che si verificano se sto enumerando in un thread e aggiungendo / rimuovendo in un altro.
Ho impostato il valore predefinito LockTimeoutsu 10 secondi, ma in realtà 1 secondo sarebbe ancora sufficiente (almeno nel mio caso utente).
Infine, questa specifica implementazione include anche INotifyCollectionChangede INotifyPropertyChanged.
public class ThreadsafeObservableCollection<T> : IList<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public event PropertyChangedEventHandler PropertyChanged;
private readonly ConcurrentQueue<PropertyChangedEventArgs> _propertyChangedEvents = new ConcurrentQueue<PropertyChangedEventArgs>();
private readonly ConcurrentQueue<NotifyCollectionChangedEventArgs> _collectionChangedEvents = new ConcurrentQueue<NotifyCollectionChangedEventArgs>();
private readonly ObservableCollection<T> _collection;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private static TimeSpan LOCK_TIMEOUT = TimeSpan.FromSeconds(10);
public ThreadsafeObservableCollection()
{
_collection = new ObservableCollection<T>();
_collection.CollectionChanged += _collection_CollectionChanged;
(_collection as INotifyPropertyChanged).PropertyChanged += _collection_PropertyChanged;
}
private void Wait()
{
while (!_semaphore.Wait(LOCK_TIMEOUT))
_semaphore.Release();
}
public void Add(T item)
{
Wait();
try
{
_collection.Add(item);
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
}
public void Clear()
{
Wait();
try
{
_collection.Clear();
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
}
public bool Contains(T item)
{
Wait();
bool result;
try
{
result = _collection.Contains(item);
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
return result;
}
public int Count
{
get
{
Wait();
int count;
try
{
count = _collection.Count;
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
return count;
}
}
public bool IsReadOnly => false;
public T this[int index]
{
get
{
Wait();
T item;
try
{
item = _collection[index];
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
return item;
}
set
{
Wait();
try
{
_collection[index] = value;
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
}
}
public void CopyTo(T[] array, int arrayIndex)
{
Wait();
try
{
_collection.CopyTo(array, arrayIndex);
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
}
public int IndexOf(T item)
{
Wait();
int index;
try
{
index = _collection.IndexOf(item);
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
return index;
}
public void Insert(int index, T item)
{
Wait();
try
{
_collection.Insert(index, item);
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
}
public bool Remove(T item)
{
Wait();
bool result;
try
{
result = _collection.Remove(item);
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
return result;
}
public void RemoveAt(int index)
{
Wait();
try
{
_collection.RemoveAt(index);
}
finally
{
_semaphore.Release();
}
FireOutstandingEvents();
}
private void FireOutstandingEvents()
{
while (_propertyChangedEvents.TryDequeue(out PropertyChangedEventArgs arg))
PropertyChanged?.Invoke(this, arg);
while (_collectionChangedEvents.TryDequeue(out NotifyCollectionChangedEventArgs arg))
CollectionChanged?.Invoke(this, arg);
}
private void _collection_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
_propertyChangedEvents.Enqueue(e);
}
private void _collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_collectionChangedEvents.Enqueue(e);
}
public IEnumerator<T> GetEnumerator()
{
Wait();
try
{
return ((IEnumerable<T>)_collection.ToArray()).GetEnumerator();
}
finally
{
_semaphore.Release();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
Wait();
try
{
return _collection.ToArray().GetEnumerator();
}
finally
{
_semaphore.Release();
}
}
}
Risposte
Il Waitmetodo non è corretto. Se il timeout scade il semaforo viene rilasciato senza essere preso. Ciò potrebbe consentire al codice di essere eseguito immediatamente dopo perché c'è uno slot disponibile (non proprio perché era già stato preso ma rilasciato erroneamente) ma in futuro quando Releaseviene chiamato fallirà perché il semaforo ha già raggiunto la sua dimensione massima.
private void Wait()
{
while (!_semaphore.Wait(LOCK_TIMEOUT))
_semaphore.Release();
}
Ogni chiamata andata a buon fine Wait deve essere associata a una chiamata a Release. Una Waitrestituzione di false non ha esito positivo.