BlockReentrancy в ObservableCollection <T>
Может кто-нибудь, пожалуйста, будьте любезны, чтобы объяснить мне, что цель метода BlockReentrancy
находится в ObservableCollection<T>
?
MSDN показывает в качестве примера следующее:
//The typical usage is to wrap an OnCollectionChanged call within a using scope, as in the following example:
using (BlockReentrancy())
{
// OnCollectionChanged call
}
Но это, похоже, не разъясняет мне, в чем цель. Кто-нибудь должен объяснить?
Ответы
Ответ 1
An ObservableCollection
реализует INotifyCollectionChanged
и поэтому имеет событие CollectionChanged
. Если есть подписчик на это событие, они могут дополнительно изменить коллекцию, пока коллекция уже находится в процессе уведомления. Поскольку событие CollectionChanged
отслеживает то, что изменилось, это взаимодействие может стать очень грязным.
В результате ObservableCollection
разрешает в качестве отдельного случая один абонент события CollectionChanged
изменять коллекцию из своего обработчика. Но запрещает изменять коллекцию из обработчика CollectionChanged
, если в событие CollectionChanged
есть два или более подписчиков.
Пара методов BlockReentrancy
и CheckReentancy
используется для реализации этой логики. BlockReentrancy
используется в начале метода OnCollectionChanged
, а CheckReentancy
используется во всех методах, которые изменяют коллекцию.
Ответ 2
Это реализация BlockReentrancy()
protected IDisposable BlockReentrancy()
{
this._monitor.Enter();
return this._monitor;
}
Существует еще один метод CheckReentrancy()
protected void CheckReentrancy()
{
if ((this._monitor.Busy && (this.CollectionChanged != null)) && (this.CollectionChanged.GetInvocationList().Length > 1))
{
throw new InvalidOperationException(SR.GetString("ObservableCollectionReentrancyNotAllowed"));
}
}
Такие методы, как ClearItems
, InsertItem
, MoveItem
, RemoveItem
, SetItem
проверяют CheckReentrancy()
перед изменением коллекции.
Таким образом, приведенный ниже код гарантирует, что коллекция не будет изменена внутри using
, но только если есть более одного обработчика, подписанного на событие CollectionChanged
.
using BlockReentrancy())
{
CollectionChanged(this, e);
}
Этот пример демонстрирует эффект BlockReentrancy()
private static void Main()
{
collection.CollectionChanged += CollectionCollectionChanged1;
collection.CollectionChanged += CollectionCollectionChanged2;
collection.Add(1);
}
private static void CollectionCollectionChanged1(object sender, NotifyCollectionChangedEventArgs e)
{
collection.Add(2); // this line will throw exception
}
private static void CollectionCollectionChanged2(object sender, NotifyCollectionChangedEventArgs e)
{
}
Ответ 3
Reentrancy - это когда метод делает что-то прямо или косвенно, что вызывает повторный вызов этого метода, возможно, рекурсивно. В этом случае используемый блок должен использоваться внутри делегата OnCollectionChanged, если вы хотите предотвратить изменение коллекции из обработчика; попытки изменить это вызовут исключение. Если вы его не использовали, любые попытки изменить коллекцию вызовут OnCollectionChanged для повторного вызова.
Ответ 4
Ниже приведен код позади BlockReentrancy. CheckReentrancy вызывается в начале каждого метода модификатора коллекции в реализации ObservableCollection.
/// <summary>
/// Disallow reentrant attempts to change this collection. E.g. an event handler
/// of the CollectionChanged event is not allowed to make changes to this collection.
/// </summary>
/// <remarks>
/// typical usage is to wrap e.g. a OnCollectionChanged call with a using() scope:
/// <code>
/// using (BlockReentrancy())
/// {
/// CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, item, index));
/// }
/// </code>
/// </remarks>
protected IDisposable BlockReentrancy()
{
_blockReentrancyCount++;
return EnsureMonitorInitialized();
}
/// <summary> Check and assert for reentrant attempts to change this collection. </summary>
/// <exception cref="InvalidOperationException"> raised when changing the collection
/// while another collection change is still being notified to other listeners </exception>
protected void CheckReentrancy()
{
if (_blockReentrancyCount > 0)
{
// we can allow changes if there only one listener - the problem
// only arises if reentrant changes make the original event args
// invalid for later listeners. This keeps existing code working
// (e.g. Selector.SelectedItems).
if (CollectionChanged?.GetInvocationList().Length > 1)
throw new InvalidOperationException(SR.ObservableCollectionReentrancyNotAllowed);
}
}
private SimpleMonitor EnsureMonitorInitialized()
{
return _monitor ?? (_monitor = new SimpleMonitor(this));
}
(Copyright (c).NET Foundation и Авторы)