我需要将ConcurrentBag作为通知。我需要知道这是安全的吗?该课程的代码是
public class NotifiableConcurrentBag<T> : ConcurrentBag<T>, INotifyCollectionChanged where T : class
{
public NotifiableConcurrentBag()
{
dispatcher = Dispatcher.CurrentDispatcher;
}
private Dispatcher dispatcher;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public new void Add(T item)
{
base.Add(item);
if (CollectionChanged != null)
{
if (Thread.CurrentThread == dispatcher.Thread)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
else
dispatcher.BeginInvoke((Action)(() =>
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
));
}
}
}
提前致谢。
答案 0 :(得分:2)
基本上不,这种方法不安全:除了线程安全问题(我没有评估其线程安全性),它留下了很多可能性,无需实际调用通知即可将项目添加到集合中。请考虑以下更简单的实现:
public class NotifiableConcurrentBag<T> : ConcurrentBag<T> where T : class {
public int CountOfAdditions { get; set; }
public new void Add(T item) {
base.Add(item);
Console.WriteLine("Overloaded!");
CountOfAdditions++;
}
}
现在,有人编写了一个棘手的代码,使用此集合不仅仅是NotifiableConcurrentBag
,而且只是ConcurrentBag
(继承),更糟糕的是,IProducerConsumerCollection
(通过继承{实现) {1}}):
ConcurrentBag
该计划的输出如下:
NotifiableConcurrentBag<string> s = new NotifiableConcurrentBag<string>();
Console.WriteLine("CHANGE --->");
s.Add("s");
ConcurrentBag<string> o = (ConcurrentBag<string>) s;
Console.WriteLine("CHANGE --->");
o.Add("s");
IProducerConsumerCollection<string> col = (IProducerConsumerCollection<string>) s;
Console.WriteLine("CHANGE --->");
col.TryAdd("s");
Console.WriteLine("Overloaded called {0} times", s.CountOfAdditions);
Console.WriteLine("In fact collection has {0} items", s.Count);
这意味着您的实施将留下两个添加而没有任何通知。为了使实现真正安全,您可能需要包装ConcurrentBag而不是继承它,并以通常始终调用通知的方式实现所需的接口。
答案 1 :(得分:2)
您正在使用方法隐藏:public new void Add(T item) { ... }
。因此,当客户端代码将NotifiableConcurrentBag<T>
实例的引用作为ConcurrentBag<T>
时,将调用错误版本的Add()
(对于ConcurrentBag<T>
)方法。
我建议您提取IConcurrentBag<T>
界面。
public interface IConcurrentBag<T> : IProducerConsumerCollection<T>
{
void Add(T item);
bool TryPeek(out T result);
bool IsEmpty { get; }
}
然后引入新的扩展接口INotifiableConcurrentBag<T>
,它实际上支持INotifyCollectionChanged
接口:
public interface INotifiableConcurrentBag<T> : IConcurrentBag<T>, INotifyCollectionChanged
{
}
之后只需实现NotifiableConcurrentBag<T>
类(更喜欢组合而不是继承)。插入通知逻辑而不是TODO标记。
public class NotifiableConcurrentBag<T> : INotifiableConcurrentBag<T>
{
private readonly ConcurrentBag<T> _concurrentBag;
public NotifiableConcurrentBag()
{
_concurrentBag = new ConcurrentBag<T>();
}
public NotifiableConcurrentBag(IEnumerable<T> collection)
{
_concurrentBag = new ConcurrentBag<T>(collection);
}
#region Implementation of IEnumerable
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
IEnumerator IEnumerable.GetEnumerator()
{
lock (((ICollection)this).SyncRoot)
{
return ((IEnumerable)_concurrentBag).GetEnumerator();
}
}
#endregion
#region Implementation of IEnumerable<T>
/// <summary>
/// Returns an enumerator that iterates through the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/>.
/// </summary>
/// <returns>
/// An enumerator for the contents of the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/>.
/// </returns>
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
lock (((ICollection)this).SyncRoot)
{
return ((IEnumerable<T>)_concurrentBag).GetEnumerator();
}
}
#endregion
#region Implementation of ICollection
/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection"/>. The <see cref="T:System.Array"/> must have zero-based indexing. </param><param name="index">The zero-based index in <paramref name="array"/> at which copying begins. </param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null. </exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than zero. </exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or- The number of elements in the source <see cref="T:System.Collections.ICollection"/> is greater than the available space from <paramref name="index"/> to the end of the destination <paramref name="array"/>.-or-The type of the source <see cref="T:System.Collections.ICollection"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception><filterpriority>2</filterpriority>
void ICollection.CopyTo(Array array, int index)
{
lock (((ICollection)this).SyncRoot)
{
((ICollection)_concurrentBag).CopyTo(array, index);
}
}
/// <summary>
/// Gets the number of elements contained in the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/>.
/// </summary>
/// <returns>
/// The number of elements contained in the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/>.
/// </returns>
int ICollection.Count
{
get
{
lock (((ICollection)this).SyncRoot)
{
return _concurrentBag.Count;
}
}
}
/// <summary>
/// Gets an object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"/>.
/// </summary>
/// <returns>
/// An object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"/>.
/// </returns>
/// <filterpriority>2</filterpriority>
object ICollection.SyncRoot
{
get { return ((ICollection)_concurrentBag).SyncRoot; }
}
/// <summary>
/// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"/> is synchronized (thread safe).
/// </summary>
/// <returns>
/// true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized (thread safe); otherwise, false.
/// </returns>
/// <filterpriority>2</filterpriority>
bool ICollection.IsSynchronized
{
get { return ((ICollection)_concurrentBag).IsSynchronized; }
}
#endregion
#region Implementation of IConcurrentBag<T>
/// <summary>
/// Adds an object to the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/>.
/// </summary>
/// <param name="item">The object to be added to the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/>. The value can be a null reference (Nothing in Visual Basic) for reference types.</param>
void IConcurrentBag<T>.Add(T item)
{
lock (((ICollection)this).SyncRoot)
{
_concurrentBag.Add(item);
// TODO: Implement notification!
}
}
/// <summary>
/// Attempts to return an object from the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/> without removing it.
/// </summary>
/// <returns>
/// true if and object was returned successfully; otherwise, false.
/// </returns>
/// <param name="result">When this method returns, <paramref name="result"/> contains an object from the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/> or the default value of <paramref name="T"/> if the operation failed.</param>
bool IConcurrentBag<T>.TryPeek(out T result)
{
lock (((ICollection)this).SyncRoot)
{
return _concurrentBag.TryPeek(out result);
}
}
/// <summary>
/// Gets a value that indicates whether the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/> is empty.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/> is empty; otherwise, false.
/// </returns>
bool IConcurrentBag<T>.IsEmpty
{
get
{
lock (((ICollection)this).SyncRoot)
{
return _concurrentBag.IsEmpty;
}
}
}
#endregion
#region Implementation of IProducerConsumerCollection<T>
/// <summary>
/// Attempts to remove and return an object from the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/>.
/// </summary>
/// <returns>
/// true if an object was removed successfully; otherwise, false.
/// </returns>
/// <param name="result">When this method returns, <paramref name="result"/> contains the object removed from the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/> or the default value of <paramref name="T"/> if the bag is empty.</param>
bool IProducerConsumerCollection<T>.TryTake(out T result)
{
lock (((ICollection)this).SyncRoot)
{
bool takeResult = _concurrentBag.TryTake(out result);
if (takeResult)
{
// TODO: Implement notification!
}
return takeResult;
}
}
/// <summary>
/// Copies the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/> elements to an existing one-dimensional <see cref="T:System.Array"/>, starting at the specified array index.
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="index">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in Visual Basic).</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than zero.</exception><exception cref="T:System.ArgumentException"><paramref name="index"/> is equal to or greater than the length of the <paramref name="array"/> -or- the number of elements in the source <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/> is greater than the available space from <paramref name="index"/> to the end of the destination <paramref name="array"/>.</exception>
void IProducerConsumerCollection<T>.CopyTo(T[] array, int index)
{
lock (((ICollection)this).SyncRoot)
{
_concurrentBag.CopyTo(array, index);
}
}
/// <summary>
/// Copies the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/> elements to a new array.
/// </summary>
/// <returns>
/// A new array containing a snapshot of elements copied from the <see cref="T:System.Collections.Concurrent.ConcurrentBag`1"/>.
/// </returns>
T[] IProducerConsumerCollection<T>.ToArray()
{
lock (((ICollection)this).SyncRoot)
{
return _concurrentBag.ToArray();
}
}
/// <summary>
/// Attempts to add an object to the <see cref="T:System.Collections.Concurrent.IProducerConsumerCollection`1"/>.
/// </summary>
/// <returns>
/// true if the object was added successfully; otherwise, false.
/// </returns>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Concurrent.IProducerConsumerCollection`1"/>.</param><exception cref="T:System.ArgumentException">The <paramref name="item"/> was invalid for this collection.</exception>
bool IProducerConsumerCollection<T>.TryAdd(T item)
{
lock (((ICollection)this).SyncRoot)
{
bool addResult = ((IProducerConsumerCollection<T>)_concurrentBag).TryAdd(item);
if (addResult)
{
// TODO: Implement notification!
}
return addResult;
}
}
#endregion
#region Implementation of INotifyCollectionChanged
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
}