Ciao a tutti

Condivido con tutti una demo che ho realizzato per un mio progetto per mostrare l’uso e la gestione dei limiti di memoria in uso all’interno della memory cache locale di C# disponibile tramite la classe System.Runtime.Caching.MemoryCache

 

Due parole sullo scenario: gestire un limite dimensionale dell’uso della RAM tramite gli oggetti in cache

L’implementazione: per implementare la cache locale si usa la MemoryCache. Questa dispone di varie configurazioni, una “default” (accessibile staticamente) e N nominali, disponibili assegnando un nome ben preciso alla cache in oggetto, per poter gestire configurazioni di tipo diverso tra le differenti cache in memoria. Noi useremo quest’ultimo esempio, anche se tutto quello che viene scritto è normalmente applicabile alla versione “default”.

 

L’esempio andrà ad accodare 10.000 oggetti da 1MB in cache generati randomicamente, tutti impostati come non più necessari dopo 1 minuto dalla loro creazione. In aggiunta, nel file di configurazione andiamo ad impostare al 5% il limite di memoria RAM effettiva da utilizzare (nel mio esempio corrisponde a 800MB circa).

Durante la creazione dei CacheItem nella cache (gli oggetti che wrappano il valore, la chiave e le relative specifiche nella cache), andiamo a registrarci all’evento che notifica la distruzione dell’oggetto nella cache. Tutti questi eventi vengono poi dirottati su una collezione che supporta la Reactive Programming, programmazione di messaggi e loro flusso, con cui possiamo specificare un throttling nella velocità di routing dei messaggi. Questo è per noi essenziale per poter invocare manualmente un Collect della Garbage Collection quando un pool di elementi dalla cache viene distrutto, dato che non avviene in automatico, e dato che non possiamo invocare il Collect per ogni singolo oggetto che viene distrutto. Con questo sistema, se un pool di 50 elementi viene distrutto contemporaneamente, i messaggi che fanno routing dell’evento di distruzione vengono accodati insieme, e solo dopo un ultimo timeout (da noi impostato a 3 secondi) viene effettivamente (ed univocamente) eseguito un singolo handler, che appunto andrà a fare il Collect.

Di seguito il codice della Console application di esempio:

var cacheRemovedRouter = new Subject<CacheEntryRemovedArguments>();

//throttlo
cacheRemovedRouter.Throttle(TimeSpan.FromSeconds(3))
    .Subscribe(a =>
    {
        GC.Collect();
        WriteLine(“Collecting free memory”);
    });

var r = new Random(DateTime.Now.GetHashCode());
var cache = new MemoryCache(“mycache”);

for (int i = 0; i < 10000; i++)
{
    var key = Guid.NewGuid().ToString();
    //check
    if (!cache.Contains(key))
    {
        //1MB buffer
        var buffer = new byte[1024 * 1024];
        //popolo il buffer
        r.NextBytes(buffer);

        cache.Add(new CacheItem(key, buffer), new CacheItemPolicy
        {
            AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(1),
            RemovedCallback = (a) =>
            {
                cacheRemovedRouter.OnNext(a);
                WriteLine($”DEL -> {a.CacheItem.Key} because of {a.RemovedReason}”);
            }
        });
    }

    Sleep(10);
}

WriteLine(“End”);
ReadLine();

 

di seguito il file di configurazione:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <startup>
    <supportedRuntime version=”v4.0″ sku=”.NETFramework,Version=v4.6.1″ />
  </startup>
  <system.runtime.caching>
    <memoryCache>
      <namedCaches>
        <add name=”mycache” pollingInterval=”00:00:30″ physicalMemoryLimitPercentage=”5″/>
      </namedCaches>
    </memoryCache>
  </system.runtime.caching>
</configuration> 

 

ed ecco il risultato

image

come visibile, dopo un attimo iniziale in cui l’euristica della cache si “avvia”, poi il ciclo di segnalazione e distruzione degli oggetti dalla cache e successivamente il Collect del GC raggiunge il risultato sperato, facendo assestare il consumo di risorse dell’applicazione esattamente a quanto desiderato

 

a presto