Я пришел на работу сегодня утром по электронной почте от друга, который дает мне два следующих ответа:
1 - Что касается работы коллекций в пространстве имен Concurrent, большинство из них предназначены для добавления и вычитания из коллекции без блокировки и являются поточно-ориентированными даже в процессе перечисления элементов коллекции.
В «обычной» коллекции получение перечислителя (через GetEnumerator) устанавливает значение «version», которое изменяется любой операцией, которая влияет на элементы коллекции (например, Add, Remove или Clear). Реализация IEnumerator будет сравнивать набор версий, когда он был создан, с текущей версией коллекции. Если отличается, генерируется исключение и перечисление прекращается.
Коллекции Concurrent разработаны с использованием сегментов, которые позволяют очень легко поддерживать многопоточность. Но в случае перечисления они фактически создают копию снимка коллекции во время вызова GetEnumerator, и перечислитель работает с этой копией. Это позволяет вносить изменения в коллекцию без негативных последствий для счетчика. Конечно, это означает, что перечисление ничего не будет знать об этих изменениях, но, похоже, ваш вариант использования позволяет это.
2 - Что касается конкретного сценария, который вы описываете, я не считаю, что необходим параллельный сбор. Вы можете обернуть стандартную коллекцию с помощью ReaderWriterLock и применять ту же логику, что и параллельные коллекции, когда вам нужно перечислить.
Вот что я предлагаю:
public class RecipientCollection
{
private Collection<Recipient> _recipients = new Collection<Recipient>();
private ReaderWriterLock _lock = new ReaderWriterLock();
public void Add(Recipient r)
{
_lock.AcquireWriterLock(Timeout.Infinite);
try
{
_recipients.Add(r);
}
finally
{
_lock.ReleaseWriterLock();
}
}
public void Remove(Recipient r)
{
_lock.AcquireWriterLock(Timeout.Infinite);
try
{
_recipients.Remove(r);
}
finally
{
_lock.ReleaseWriterLock();
}
}
public IEnumerable<Recipient> ToEnumerable()
{
_lock.AcquireReaderLock(Timeout.Infinite);
try
{
var list = _recipients.ToArray();
return list;
}
finally
{
_lock.ReleaseReaderLock();
}
}
}
ReaderWriterLock гарантирует, что операции блокируются, только если выполняется другая операция, которая изменяет содержимое коллекции. Как только эта операция завершается, блокировка снимается, и следующая операция может продолжаться.
Ваш механизм предупреждений будет использовать метод ToEnumerable (), чтобы получить копию снимка коллекции в то время и перечислить копию.
В зависимости от того, как часто отправляется оповещение и вносятся изменения в коллекцию, это может быть проблемой, но вы все равно сможете реализовать некоторый тип свойства версии, который изменяется при добавлении или удалении элемента и оповещении Движок может проверить это свойство, чтобы узнать, нужно ли ему снова вызывать ToEnumerable () для получения последней версии. Или инкапсулируйте это, кэшируя массив внутри класса RecipientCollection и аннулируя кеш при добавлении или удалении элемента.
НТН