C# 如何安全地调用可由其他线程更改的IEnumerable上的Count(Func)?
我的WPF元素之一是绑定到属性的数据,该属性在C# 如何安全地调用可由其他线程更改的IEnumerable上的Count(Func)?,c#,wpf,multithreading,linq,parallel-processing,C#,Wpf,Multithreading,Linq,Parallel Processing,我的WPF元素之一是绑定到属性的数据,该属性在IEnumerable上调用Count(Func)。该属性显示系统中活动实体的计数(因此需要Func参数) 。 但是,IEnumerable是一个列表,其他线程可以随时更改该列表。有时,应用程序崩溃,显然是因为IEnumerable在Count函数枚举过程中被修改 我可以放置一个try-catch块,但是如何获取该属性返回的值呢 注意:使用lock可能不太实际,因为我不知道访问IEnumerable的所有代码。您需要某种方法来控制并发,或提供快照,或
IEnumerable
上调用Count(Func)
。该属性显示系统中活动实体的计数(因此需要Func
参数)
。
但是,IEnumerable
是一个列表,其他线程可以随时更改该列表。有时,应用程序崩溃,显然是因为IEnumerable
在Count
函数枚举过程中被修改
我可以放置一个try-catch
块,但是如何获取该属性返回的值呢
注意:使用lock可能不太实际,因为我不知道访问IEnumerable
的所有代码。您需要某种方法来控制并发,或提供快照,或使用并发安全集合(如中的那些)。反复尝试该操作、捕获异常并重试可能会奏效,但这非常可怕
集合是否可以在此类更改时通知此类?这样,您就可以将计数保留为字段,而不是每次需要获取属性时都遍历它,并以线程安全的方式保留计数。如果一个属性中的集合变得很大,那么对该集合进行计数就有点麻烦了。(当然,如果它实现了IList
,那么调用也会得到优化……但这是另一回事。)
如果您能告诉我们有关上下文的更多信息,我们可能会提供更多帮助。您需要某种方法来控制并发,或提供快照,或使用并发安全集合(如中的集合)。反复尝试该操作、捕获异常并重试可能会奏效,但这非常可怕
集合是否可以在此类更改时通知此类?这样,您就可以将计数保留为字段,而不是每次需要获取属性时都遍历它,并以线程安全的方式保留计数。如果一个属性中的集合变得很大,那么对该集合进行计数就有点麻烦了。(当然,如果它实现了IList
,那么调用也会得到优化……但这是另一回事。)
如果您能告诉我们更多关于上下文的信息,我们可能会提供更多帮助。您可以在对列表运行
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);
}
}
公共类MyCollection:集合
{
私有只读对象syncRoot=新对象();
受保护的覆盖无效集合项(整数索引,T项)
{
锁定(同步根)
base.SetItem(索引,项);
}
受保护的覆盖无效插入项(int索引,T项)
{
锁定(同步根)
基本插入项(索引,项目);
}
受保护的覆盖无效ClearItems()
{
锁定(同步根)
base.ClearItems();
}
受保护的覆盖void removietem(int索引)
{
锁定(同步根)
基本删除项(索引);
}
公共新整数计数(Func谓词)
{
锁定(同步根)
返回可枚举的.Count(this,谓词);
}
}
您可以通过在列表上运行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);
}
}
公共类MyCollection:集合
{
私有只读对象syncRoot=新对象();
受保护的覆盖无效集合项(整数索引,T项)
{
锁定(同步根)
base.SetItem(索引,项);
}
受保护的覆盖无效插入项(int索引,T项)
{
锁定(同步根)
基本插入项(索引,项目);
}
受保护的覆盖无效ClearItems()
{
锁定(同步根)
base.ClearItems();
}
受保护的覆盖void removietem(int索引)
{
锁定(同步根)
基本删除项(索引);
}
公共新整数计数(Func谓词)
{
锁定(同步根)
返回可枚举的.Count(this,谓词);
}
}
您正在以不受支持的方式使用IEnumerable。枚举的源在枚举“打开”或正在使用的生存期内不应更改。句号
一种解决方案是要求IEnumeration的所有使用者和它所来自的数据源在出于任何原因访问数据之前使用互斥锁。笨手笨脚的,吵闹的
另一种解决方案是维护自己的Count属性,每当数据源发生更改时,该属性都会更新(以线程安全的方式)。这样,您就不需要IEnumerable.Count()。您正在以不受支持的方式使用IEnumerable。枚举的源在枚举“打开”或正在使用的生存期内不应更改。句号 一种解决方案是要求IEnumeration的所有使用者和它所来自的数据源在出于任何原因访问数据之前使用互斥锁。笨手笨脚的,吵闹的
另一种解决方案是维护自己的Count属性,每当数据源发生更改时,该属性都会更新(以线程安全的方式)。这样,您就不需要IEnumerable.Count()。您可以将try/catch放在属性中,然后重试或在失败时返回-1。仍然不是一个很好的解决方案(@leppie同意。我的GUI将显示“-1”,这真的很奇怪。您可以将try/catch放在属性中,然后重试或在失败时返回-1。仍然不是预处理