J'ai récemment eu à vérifier dans cette monstruosité dans le code de production de manipuler les champs privés dans un WPF classe: (tl;dr comment puis-je éviter d'avoir à le faire?)
private static class MemoryPressurePatcher
{
private static Timer gcResetTimer;
private static Stopwatch collectionTimer;
private static Stopwatch allocationTimer;
private static object lockObject;
public static void Patch()
{
Type memoryPressureType = typeof(Duration).Assembly.GetType("MS.Internal.MemoryPressure");
if (memoryPressureType != null)
{
collectionTimer = memoryPressureType.GetField("_collectionTimer", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as Stopwatch;
allocationTimer = memoryPressureType.GetField("_allocationTimer", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as Stopwatch;
lockObject = memoryPressureType.GetField("lockObj", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null);
if (collectionTimer != null && allocationTimer != null && lockObject != null)
{
gcResetTimer = new Timer(ResetTimer);
gcResetTimer.Change(TimeSpan.Zero, TimeSpan.FromMilliseconds(500));
}
}
}
private static void ResetTimer(object o)
{
lock (lockObject)
{
collectionTimer.Reset();
allocationTimer.Reset();
}
}
}
Pour comprendre pourquoi je ferais quelque chose de tellement fou, vous avez besoin de regarder MS.Internal.MemoryPressure.ProcessAdd()
:
/// <summary>
/// Check the timers and decide if enough time has elapsed to
/// force a collection
/// </summary>
private static void ProcessAdd()
{
bool shouldCollect = false;
if (_totalMemory >= INITIAL_THRESHOLD)
{
// need to synchronize access to the timers, both for the integrity
// of the elapsed time and to ensure they are reset and started
// properly
lock (lockObj)
{
// if it's been long enough since the last allocation
// or too long since the last forced collection, collect
if (_allocationTimer.ElapsedMilliseconds >= INTER_ALLOCATION_THRESHOLD
|| (_collectionTimer.ElapsedMilliseconds > MAX_TIME_BETWEEN_COLLECTIONS))
{
_collectionTimer.Reset();
_collectionTimer.Start();
shouldCollect = true;
}
_allocationTimer.Reset();
_allocationTimer.Start();
}
// now that we're out of the lock do the collection
if (shouldCollect)
{
Collect();
}
}
return;
}
L'important est près de l'extrémité, où il appelle la méthode Collect()
:
private static void Collect()
{
// for now only force Gen 2 GCs to ensure we clean up memory
// These will be forced infrequently and the memory we're tracking
// is very long lived so it's ok
GC.Collect(2);
}
Oui, c'est WPF forcer un gen 2 la collecte des ordures, où les forces d'un blocage complet du GC. Naturellement GC arrive sans blocage sur la gen 2 tas. Ce que cela signifie en pratique que lorsque cette méthode est appelée, l'ensemble de notre application se bloque. Plus la mémoire de votre application à l'aide de, et la de plus en plus fragmenté votre gen 2 tas est, plus il faudra. Notre application actuellement caches un peu de données et peut facilement prendre un gig de mémoire et le travail forcé GC permet de verrouiller notre application sur un lent appareil pendant plusieurs secondes, chaque 850 MME
Car, en dépit de l'auteur protestations du contraire, il est facile d'en arriver à un scénario où cette méthode est appelée avec une grande fréquence. Ce mémoire de code de WPF est produit lors du chargement d'un BitmapSource
à partir d'un fichier. Nous virtualiser un listview avec des milliers d'articles où chaque élément est représenté par une vignette stockées sur le disque. Comme nous l'avons faites défiler vers le bas, nous sommes le chargement dynamique dans ces vignettes, et que la GC qui se passe à la fréquence maximale. Afin de défilement devient incroyablement lent et saccadé avec l'app verrouillage vers le haut en permanence.
Avec ce terrible réflexion hack je l'ai mentionné en haut, on force les compteurs à zéro pour ne jamais être atteint, et donc WPF jamais les forces de la GC. En outre, il semble y avoir pas de conséquences négatives à-dire la mémoire se développe comme l'un des parchemins et éventuellement un GC est déclenchée naturellement, sans bloquer le thread principal.
Est-il une autre option pour éviter que ces appels à l' GC.Collect(2)
qui n'est pas si flagrante hideux comme ma solution? Aimerais obtenir une explication de ce que le béton sont les problèmes qui peuvent découler de la suivre à travers avec ce hack. Je veux dire par là des problèmes d'éviter l'appel à GC.Collect(2)
. (me semble que le GC naturelle devrait être suffisant)