Koleksi Observasi 'ThreadSafe' - C #
Melihat sebagai koleksi bersamaan tidak ada di .net yang memungkinkan item tertentu untuk dihapus, saya telah mengumpulkan kelas berikut.
Penting untuk dicatat bahwa ini hanya threadsafe sampai operasi LockTimeoutpada suatu waktu berakhir.
Tujuan utamanya adalah untuk menjaga dari pengecualian 'InvalidOperationException: Collection Was Modified' yang nakal, yang terjadi jika saya menghitung di satu utas, dan menambahkan / menghapus di utas lain.
Saya telah menetapkan default LockTimeoutmenjadi 10 detik, tetapi kenyataannya, 1 detik masih akan banyak (setidaknya dalam kasus pengguna saya).
Terakhir, implementasi khusus ini juga menggabungkan INotifyCollectionChangeddan 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();
}
}
}
Jawaban
The Waitmetode adalah tidak benar. Jika batas waktu berakhir semafor dilepaskan tanpa diambil. Ini memungkinkan kode untuk dijalankan segera setelah itu karena ada slot yang tersedia (tidak benar-benar seperti yang telah diambil tetapi dilepaskan secara keliru) tetapi di masa depan ketika Releasedipanggil itu akan gagal karena semaphore telah mencapai ukuran maksimumnya.
private void Wait()
{
while (!_semaphore.Wait(LOCK_TIMEOUT))
_semaphore.Release();
}
Setiap panggilan ke yang berhasil Wait harus dipasangkan dengan panggilan ke Release. Salah Waitkembali tidak berhasil.