Obserwowalna kolekcja „ThreadSafe” - C #
Ponieważ w .net nie istnieje kolekcja współbieżna, która pozwala na usuwanie określonych elementów, zestawiłem następującą klasę.
Ważne jest, aby pamiętać, że ochrona wątków jest bezpieczna tylko do momentu LockTimeoutwygaśnięcia danej operacji.
Głównym celem było zabezpieczenie przed bezczelnymi wyjątkami „InvalidOperationException: Collection Was Modified”, które występują, gdy wyliczam w jednym wątku, a dodam / usuwam w innym.
Domyślnie ustawiłem na LockTimeout10 sekund, ale w rzeczywistości 1 sekunda byłaby nadal wystarczająca (przynajmniej w moim przypadku).
Wreszcie, ta konkretna implementacja obejmuje również INotifyCollectionChangedi 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();
}
}
}
Odpowiedzi
WaitMetoda nie jest poprawne. Jeśli limit czasu wygaśnie, semafor jest zwalniany bez użycia. Może to pozwolić na natychmiastowe uruchomienie kodu, ponieważ jest dostępne miejsce (nie tak naprawdę, ponieważ zostało już zajęte, ale błędnie zwolnione), ale w przyszłości Releasewywołanie zakończy się niepowodzeniem, ponieważ semafor osiągnął już maksymalny rozmiar.
private void Wait()
{
while (!_semaphore.Wait(LOCK_TIMEOUT))
_semaphore.Release();
}
Każde udane połączenie z numerem Wait musi być powiązane z połączeniem z numerem Release. WaitPowrocie fałsz nie jest udane.