[EDIT]Performance en ce qui concerne la mise en œuvre <T> mises en cache IEnumerable
Le nouveau Reactive Framework résout le problème décrit ci-dessous, en utilisant la méthode d'extension System.Linq.EnumerableEx.MemoizeAll()
.
En interne, MemoizeAll()
utilise un System.Linq.EnumerableEx.MemoizeAllEnumerable<T>
(trouvé dans l'assembly System.Interactive), qui est similaire à mon ThreadSafeCachedEnumerable<T>
(sorta).
Voici un exemple qui imprime drôlement arrangé le contenu d'un Enumerable (numéros 1-10) très lentement, puis imprime rapidement le contenu d'une seconde fois (car il cache les valeurs):
// 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]
Bonjour gourous multi-threading,
J'ai créé la classe ThreadSafeCachedEnumerable l'intention d'augmenter les performances où les longues requêtes en cours d'exécution où réutilisés. L'idée était d'obtenir un énumérateur à partir d'un IEnumerable et d'ajouter des éléments à un cache à chaque appel à MoveNext(). Ce qui suit est mon implémentation actuelle:
/// <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
}
I simplement « verrouillage (this.enumerator) » lorsque les pas plus d'éléments semblent être dans le cache, juste au cas où un autre thread est sur le point de ajouter un autre élément (je suppose qu'appeler MoveNext() sur this.enumerator à partir de deux threads est une mauvaise idée).
La performance est grande lors de la récupération des éléments précédemment mises en cache, mais il commence à souffrir lors de l'obtention de nombreux articles pour la première fois (en raison du blocage constant). Des suggestions pour augmenter la performance?
Merci!
Je ne vois ni «MemoizeAll», ni «MemoizeAllEnumerable» dans [codebase Rx] (https://github.com/Reactive-Extensions/Rx.NET). Cela étant dit, votre 'ThreadSafeCachedEnumerable' a oublié d'appeler dispose() sur l'énumérateur source, qui est un IDisposable. Ceci est habituellement la responsabilité des méthodes 'foreach' ou Linq, mais puisque maintenant vous en obtenez un, vous devriez être responsable de l'éliminer. – KFL