How to safely call Count(Func) on an IEnumerable that can be changed by another thread?
One of my WPF element is data-bound to a property that calls Count(开发者_开发百科Func)
on a IEnumerable
. The property displays something like the count of active entities in the system (thus the need of a Func
parameter).
public int ActiveEntitiesCount
{
get
{
return Entities.Count((item) =>
{
//my own code, just a couple of value comparisons, very quick
});
}
}
.
However the IEnumerable
is a list that can be changed by another thread at any time. Sometimes, the application crashes, apparently because the IEnumerable
is modified while the Count
function enumeration is in progress.
I can put a try-catch
block, but how to get the value to be returned by that property?
Note: Using lock may be not practical, because I don't know all the codes that's accessing the IEnumerable
You can alleviate the issue by making a snapshot of your list before running Count
on it. Like:
return Entities.ToList().Count(item => ...
To truly fix the issue, you would either make it thread-safe, using any of synchronization technique or to redesign it to avoid contention.
[EDIT] Possible solution:
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);
}
}
You'll need some way of controlling the concurrency, or providing a snapshot, or using a concurrency-safe collection (like the ones in System.Collections.Concurrent
). Repeatedly trying the operation, catching the exception and retrying is likely to work, but it's pretty horrible.
Is it possible for the collection to notify this class when it changes? That way you could keep the count as a field instead of walking through it each time you need to get the property, and keep the count in a thread-safe way. Counting a collection within a property becomes a bit of a smell if the collection becomes large. (It's okay if it implements IList<T>
of course, as then the call will be optimized anyway... but that's a different matter.)
If you could tell us more about the context, we may be able to help more.
You're using IEnumerable in a way that is not supported. The source of an enumeration is not supposed to change for the lifetime that the enumeration is "open" or being used. Period.
One solution would be to require all consumers of the IEnumeration and the data source it comes from to take a mutex lock before accessing the data for any reason. Heavy handed and noisy.
Another solution would be to maintain your own Count property which is updated (in a thread safe manner) whenever the data source changes. This way, you don't need IEnumerable.Count().
精彩评论