【问题标题】:BlockReentrancy in ObservableCollection<T>ObservableCollection<T> 中的块重入
【发布时间】:2011-06-06 02:58:45
【问题描述】:

有人可以向我解释BlockReentrancy 方法在ObservableCollection&lt;T&gt; 中的用途吗?

MSDN 显示以下示例:

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

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

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

【问题讨论】:

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

标签: c# .net events collections observablecollection


【解决方案1】:

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

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

BlockReentrancyCheckReentancy 这对方法用于实现这个逻辑。 BlockReentrancy 用于OnCollectionChanged 方法的开头,CheckReentancy 用于修改集合的所有方法。

【讨论】:

  • 非常有趣,MSDN 文档没有提到这个小事实,即只有当 CollectionChanged 事件附加了多个处理程序时,CheckReentrancy 才会阻止重入。
  • “如果 CollectionChanged 事件有两个或更多订阅者,则不允许从 CollectionChanged 处理程序修改集合。”如果只有一个订阅者但有两个工作线程在修改集合怎么办?在这种情况下,我是否应该使用另一个锁来锁定插入和删除等操作并锁定 OnCollectionChanged 处理程序?
【解决方案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"));
    }
}

ClearItemsInsertItemMoveItemRemoveItemSetItem等方法在修改集合之前检查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)
{
}

【讨论】:

  • 您的演示代码将抛出 StackOverflowException 而不是 InvalidOperationException。在这种情况下,不会检查可重入性。看我的回答。
【解决方案3】:

下面是 BlockReentrancy 后面的code。在 ObservableCollection 的实现中,每个集合修饰符方法开始时都会调用 CheckReentrancy。

    /// <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 基金会和贡献者)

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-28
      • 2011-09-09
      • 2023-04-05
      • 1970-01-01
      • 2011-04-04
      相关资源
      最近更新 更多