Наблюдаемая коллекция ThreadSafe - C #
Поскольку в .net не существует параллельной коллекции, которая позволяет удалять определенные элементы, я собрал следующий класс.
Важно отметить, что потокобезопасен только до LockTimeoutистечения срока действия данной операции.
Основная цель состояла в том, чтобы защитить себя от дерзких исключений InvalidOperationException: Collection Was Modified, которые возникают, если я перечисляю в одном потоке и добавляю / удаляю в другом.
Я установил LockTimeoutпо умолчанию 10 секунд, но на самом деле 1 секунды все равно будет достаточно (по крайней мере, в моем случае пользователя).
Наконец, эта конкретная реализация также включает INotifyCollectionChangedи 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();
}
}
}
Ответы
WaitМетод не является правильным. Если время ожидания истекает, семафор освобождается, но не используется. Это может позволить коду запускаться сразу после этого, потому что есть доступный слот (не совсем так, как он уже был занят, но был ошибочно освобожден), но в будущем, когда он Releaseвызывается, он завершится ошибкой, потому что семафор уже достиг своего максимального размера.
private void Wait()
{
while (!_semaphore.Wait(LOCK_TIMEOUT))
_semaphore.Release();
}
Каждый успешный вызов Wait должен сопровождаться вызовом Release. WaitВозвращение ложным не является успешным.