ObservableCollection<T>中的 BlockReentrancy

BlockReentrancy in ObservableCollection<T>

提问人:Maxim Gershkovich 提问时间:6/6/2011 更新时间:1/18/2019 访问量:7480

问:

有人可以向我解释该方法的目的是什么吗?BlockReentrancyObservableCollection<T>

MSDN 显示以下内容作为示例:

//The typical usage is to wrap an OnCollectionChanged call within a using scope, as in the following example:

using (BlockReentrancy())
{
    // OnCollectionChanged call
}

但这似乎并没有为我澄清目的是什么。有人愿意解释吗?

C# .NET 事件 集合 ObservableCollection

评论

0赞 Colonel Panic 8/5/2013
发音BlockReëntrancy
0赞 DK. 7/26/2019
除了公认的答案之外,值得一提的是,在多线程用例中,当集合更新如此频繁以至于干扰处理时,也需要这样做。这可能只发生一个 XAML 绑定,并且自定义代码中没有任何其他显式订阅者。using (BlockReentrancy()) {}OnCollectionChanged

答:

13赞 Alex Aza 6/6/2011 #1

这是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"));
    }
}

修改集合前检查、ClearItemsInsertItemMoveItemRemoveItemSetItemCheckReentrancy()

因此,下面的代码保证集合不会在 中更改,但前提是有多个处理程序订阅了事件。usingCollectionChanged

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)
{
}

评论

1赞 Rick Sladkey 6/6/2011
您的演示代码将抛出一个 not an .在这种情况下,不会检查重入性。看我的答案。StackOverflowExceptionInvalidOperationException
3赞 x0n 6/6/2011 #2

重入是指方法直接或间接执行某些操作,导致该方法再次被调用,可能是递归方式。在这种情况下,如果要防止从处理程序中更改集合,则应在 OnCollectionChanged 委托中使用 using 块;尝试更改它将引发异常。如果未使用它,则任何修改集合的尝试都会导致再次调用 OnCollectionChanged。

28赞 Rick Sladkey 6/6/2011 #3

一个实现,所以它有一个事件。如果存在此事件的订阅者,则他们可以在集合已在通知过程中时进一步修改集合。由于事件会准确跟踪更改的内容,因此这种交互可能会变得非常混乱。ObservableCollectionINotifyCollectionChangedCollectionChangedCollectionChanged

因此,作为特殊情况,允许事件的单个订阅者从其处理程序修改集合。但是,如果事件有两个或多个订阅者,则不允许从处理程序修改集合。ObservableCollectionCollectionChangedCollectionChangedCollectionChanged

用于实现此逻辑的一对方法。在方法的开头使用 ,并用于修改集合的所有方法。BlockReentrancyCheckReentancyBlockReentrancyOnCollectionChangedCheckReentancy

评论

0赞 treaschf 12/27/2012
非常有趣的是,MSDN 文档没有提到这个小事实,即仅当有多个处理程序附加到 CollectionChanged 事件时,CheckReentrancy 才会阻止重入。
1赞 Felix 1/7/2014
“如果有两个或多个订阅者访问 CollectionChanged 事件,则不允许从 CollectionChanged 处理程序修改集合。”如果只有一个订阅者,但有两个工作线程修改集合,该怎么办?在这种情况下,我是否应该使用花药锁来锁定 Insert 和 Remove 等操作,并锁定 OnCollectionChanged 处理程序?
1赞 Mike DeMauro 1/18/2019 #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's 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));
    }

(版权所有 (c) .NET Foundation 和贡献者)