[ИЗМЕНИТЬ]
Новая Reactive Framework решает описанную ниже проблему, используя расширение System.Linq.EnumerableEx.MemoizeAll()
. метод.
Внутри MemoizeAll()
используется System.Linq.EnumerableEx.MemoizeAllEnumerable<T>
(найденный в сборке System.Interactive), который похож на мой ThreadSafeCachedEnumerable<T>
(вроде).
Вот ужасно надуманный пример, который печатает содержимое 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);
}
[/РЕДАКТИРОВАТЬ]
Здравствуйте, гуру многопоточности!
Я создал класс 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)», когда в кеше больше нет элементов, на тот случай, если другой поток собирается добавить еще один элемент (я предполагаю, что вызов MoveNext() для this.enumerator из двух потоков плохая идея).
Производительность отличная при извлечении ранее кэшированных элементов, но она начинает страдать при первом получении большого количества элементов (из-за постоянной блокировки). Есть предложения по увеличению производительности?
Спасибо!
MemoizeAll
, ниMemoizeAllEnumerable
в кодовой базе Rx. При этом вашThreadSafeCachedEnumerable
забыл вызвать dispose() для исходного перечислителя, который является IDisposable. Обычно за это отвечают методыforeach
или Linq, но, поскольку теперь вы их получили, вы должны нести ответственность за их удаление. - person KFL   schedule 02.04.2015