[编辑]
新的Reactive Framework使用System.Linq.EnumerableEx.MemoizeAll()
扩展方法解决了下面列出的问题。
在内部,MemoizeAll()
使用System.Linq.EnumerableEx.MemoizeAllEnumerable<T>
(在System.Interactive程序集中找到),类似于我的ThreadSafeCachedEnumerable<T>
(sorta)。
这是一个非常人为的例子,它非常缓慢地打印出Enumerable(数字1-10)的内容,然后第二次快速打印内容(因为它缓存了值):
// Create an Enumerable<int> containing numbers 1-10, using Thread.Sleep() to simulate work
var slowEnum = EnumerableEx.Generate(1, currentNum => (currentNum <= 10), currentNum => currentNum, previousNum => { Thread.Sleep(250); return previousNum + 1; });
// This decorates the slow enumerable with one that will cache each value.
var cachedEnum = slowEnum.MemoizeAll();
// Print the numbers
foreach (var num in cachedEnum.Repeat(2))
{
Console.WriteLine(num);
}
[/ EDIT]
Hello多线程大师,
我创建了ThreadSafeCachedEnumerable类,旨在提高长时间运行的重用查询的性能。我们的想法是从IEnumerable获取一个枚举器,并在每次调用MoveNext()时将项添加到缓存中。以下是我目前的实施:
/// <summary>
/// Wraps an IEnumerable<T> and provides a thread-safe means of caching the values."/>
/// </summary>
/// <typeparam name="T"></typeparam>
class ThreadSafeCachedEnumerable<T> : IEnumerable<T>
{
// An enumerator from the original IEnumerable<T>
private IEnumerator<T> enumerator;
// The items we have already cached (from this.enumerator)
private IList<T> cachedItems = new List<T>();
public ThreadSafeCachedEnumerable(IEnumerable<T> enumerable)
{
this.enumerator = enumerable.GetEnumerator();
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
// The index into the sequence
int currentIndex = 0;
// We will break with yield break
while (true)
{
// The currentIndex will never be decremented,
// so we can check without locking first
if (currentIndex < this.cachedItems.Count)
{
var current = this.cachedItems[currentIndex];
currentIndex += 1;
yield return current;
}
else
{
// If !(currentIndex < this.cachedItems.Count),
// we need to synchronize access to this.enumerator
lock (enumerator)
{
// See if we have more cached items ...
if (currentIndex < this.cachedItems.Count)
{
var current = this.cachedItems[currentIndex];
currentIndex += 1;
yield return current;
}
else
{
// ... otherwise, we'll need to get the next item from this.enumerator.MoveNext()
if (this.enumerator.MoveNext())
{
// capture the current item and cache it, then increment the currentIndex
var current = this.enumerator.Current;
this.cachedItems.Add(current);
currentIndex += 1;
yield return current;
}
else
{
// We reached the end of the enumerator - we're done
yield break;
}
}
}
}
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
我只是“锁定(this.enumerator)”当没有其他项目似乎在缓存中时,以防另一个线程即将添加另一个项目(我假设在this.enumerator上调用MoveNext()两个线程是一个坏主意)。
检索以前缓存的项目时性能很好,但是第一次获取多个项目时(由于常量锁定),它开始受到影响。有关提高性能的建议吗?
谢谢!
答案 0 :(得分:7)
一些建议:
Dictionary
或HashSet
。同样,可以在调用之间删除项目,使缓存无效。答案 1 :(得分:2)
在.NET中锁定通常非常快(如果没有争用)。分析是否将锁定识别为性能问题的根源?在底层枚举器上调用MoveNext
需要多长时间?
此外,代码现在不是线程安全的。在另一个线程(this.cachedItems[currentIndex]
)上调用if (currentIndex < this.cachedItems.Count)
时,您无法安全地调用this.cachedItems.Add(current)
。从List(T) documentation:“A List(T)可以同时支持多个读者,只要不修改集合。”为了保证线程安全,您需要使用锁保护对this.cachedItems
的所有访问(如果一个或多个线程有可能修改它)。