如何安全地调用可被另一个线程更改的IEnumerable上的Count(Func)?

时间:2011-06-28 06:15:51

标签: c# wpf multithreading linq parallel-processing

我的一个WPF元素数据绑定到一个在Count(Func)上调用IEnumerable的属性。该属性显示类似于系统中活动实体的计数(因此需要Func参数)。

public int ActiveEntitiesCount
{
    get
    {
        return Entities.Count((item) =>
        {
            //my own code, just a couple of value comparisons, very quick
        });
    }
}

。 但是IEnumerable是一个列表,可以随时由另一个线程更改。有时,应用程序崩溃,显然是因为在IEnumerable函数枚举正在进行时修改了Count

我可以放一个try-catch块,但是如何获取该属性返回的值?

注意:使用锁定可能不切实际,因为我不知道访问IEnumerable的所有代码

3 个答案:

答案 0 :(得分:3)

你需要一些控制并发的方法,使用并发安全集合提供快照,(如System.Collections.Concurrent中的那些。反复尝试操作,捕获异常并重试可能会起作用,但它非常可怕。

集合是否可以在更改时通知此类?这样,您可以将计数保持为字段,而不是每次需要获取属性时都将其记录下来,并以线程安全的方式保留 count 。如果集合变大,计算属性中的集合会变得有点气味。 (如果它实现IList<T>当然没关系,因为无论如何调用都会被优化......但这是另一回事。)

如果你能告诉我们更多关于背景的信息,我们可能会提供更多帮助。

答案 1 :(得分:3)

您可以通过在列表上运行Count之前创建列表快照来缓解此问题。像:

return Entities.ToList().Count(item => ...

要真正解决问题,您可以使用任何同步技术使其成为线程安全的,或者重新设计它以避免争用。

[编辑] 可能的解决方案:

public class MyCollection<T> : Collection<T>
{
    private readonly object syncRoot = new object();

    protected override void SetItem(int index, T item)
    {
        lock (syncRoot)
            base.SetItem(index, item);
    }

    protected override void InsertItem(int index, T item)
    {
        lock (syncRoot)
            base.InsertItem(index, item);
    }

    protected override void ClearItems()
    {
        lock (syncRoot)
            base.ClearItems();
    }

    protected override void RemoveItem(int index)
    {
        lock (syncRoot)
            base.RemoveItem(index);
    }

    public new int Count(Func<T, bool> predicate)
    {
        lock (syncRoot)
            return Enumerable.Count(this, predicate);
    }
}

答案 2 :(得分:0)

您正在以不受支持的方式使用IEnumerable。枚举的来源不应该在枚举“打开”或被使用的生命周期中发生变化。周期。

一种解决方案是要求IEnumeration的所有使用者及其来源的数据源在出于任何原因访问数据之前采用互斥锁。笨手笨脚。

另一种解决方案是维护自己的Count属性,每当数据源发生变化时,都会更新(以线程安全的方式)。这样,您就不需要IEnumerable.Count()。