2 votes

Comment mettre en cache l'initialisation lente des ressources à partir du serveur C# Web API REST ?

Contexte

J'essaie de mettre en œuvre un service Web API REST qui "enveloppe" un programme C existant.

Problème / Objectif

Étant donné que le programme C a un temps d'initialisation lent et une utilisation élevée de la RAM lorsque je lui demande d'ouvrir un dossier spécifique (je suppose que cela ne peut pas être amélioré), je pense mettre en cache le handle/objet C, de sorte que la prochaine fois qu'une requête GET atteint le même dossier, je peux utiliser le handle existant.

Ce que j'ai essayé

Déclarez tout d'abord un dictionnaire statique permettant de mettre en correspondance le chemin du dossier et la poignée :

static ConcurrentDictionary<string, IHandle> handles = new ConcurrentDictionary<string, IHandle>();

Dans ma fonction GET :

IHandle theHandle = handles.GetOrAdd(dir.Name, x => {
    return new Handle(x); //this is the slow and memory-intensive function
});

De cette façon, chaque fois qu'un dossier spécifique a fait l'objet d'un GET auparavant, il aura déjà un identifiant prêt à être utilisé.

Pourquoi ce n'est pas bon

Je risque donc maintenant de manquer de mémoire si trop de dossiers sont mis en cache simultanément. Comment pourrais-je ajouter un processus d'arrière-plan de type GC pour TryRemove() et appeler IHandle.Dispose() sur les vieilles poignées, peut-être dans le cadre d'une politique du moins récemment utilisé ou du moins fréquemment utilisé ? Idéalement, il devrait commencer à se déclencher uniquement lorsque la mémoire physique disponible est faible.

J'ai essayé d'ajouter l'instruction suivante dans la fonction GET, mais cela semble trop compliqué et sa fonction est très limitée. Cette méthode ne fonctionne bien que si je veux que les poignées expirent toujours au bout de 10 secondes, et elle ne redémarre pas la minuterie si une nouvelle demande arrive dans les 10 secondes.

HostingEnvironment.QueueBackgroundWorkItem(ct =>
{
    System.Threading.Thread.Sleep(10000);
    if (handles.TryRemove(dir.Name, out var handle2))
        handle2.Dispose();
});

Ce que cette question n'est pas

Je ne pense pas que la mise en cache de la sortie soit la solution ici. Après avoir retourné le résultat de cette requête GET (il ne s'agit que des métadonnées du contenu du dossier), il se peut qu'il y ait une autre requête GET pour des données plus approfondies, ce qui nécessite l'appel à Handle Les méthodes de l'UE.

J'espère que ma question est suffisamment claire !

0voto

Ucho Points 124
  1. Gère la fermeture en cas de mémoire faible.

    ConcurrentQueue<(string, IHandle)> handles = new ConcurrentQueue<(string, IHandle)>();

    void CheckMemory_OptionallyReleaseOldHandles() { var performance = new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes");

    while (performance.NextValue() <= YOUR_TRESHHOLD) { if (handles.TryDequeue(out ValueTuple<string, IHandle> value)) { value.Item2.Dispose(); } } }

  2. Votre méthode Get.

    IHandle GetHandle() { IHandle theHandle = handles.FirstOrDefault(v => v.Item1 == dir.Name).Item2;

    if (theHandle == null) { theHandle = new Handle(dir.Name); handles.Enqueue((dir.Name, theHandle)); }

    return theHandle; });

  3. Votre tâche de fond.

    void SetupMemoryCheck() {

    Action<CancellationToken> BeCheckingTheMemory = ct => {

    for(;;)
    {
      if (ct.IsCancellationRequested)
      {
        break;
      }
    
      CheckMemory_OptionallyReleaseOldHandles();
      Thread.Sleep(500);
    };

    };

    HostingEnvironment.QueueBackgroundWorkItem(ct => { var tf = new TaskFactory(ct, TaskCreationOptions.LongRunning, TaskContinuationOptions.None, TaskScheduler.Current); tf.StartNew(() => BeCheckingTheMemory(ct)); }); }

Je suppose que la collection aura peu d'éléments et qu'il n'y a pas besoin de dictionnaire.

0voto

Ucho Points 124

Je n'ai pas compris votre demande de LRU/LFU la première fois. Ici, vous pouvez vérifier un modèle de cache hybride LRU/LFU.

  1. Gère la fermeture en cas de mémoire faible.

    /*

    • string – handle name,
    • IHandle – the handle,
    • int – hit count, */ ConcurrentDictionary<string, (IHandle, int)> handles = new ConcurrentDictionary<string, (IHandle, int)>();

    void FreeResources() { if (handles.Count == 0) { return; }

    var performance = new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes");

    while (performance.NextValue() <= YOUR_TRESHHOLD) { int maxIndex = (int)Math.Ceiling(handles.Count / 2.0d); KeyValuePair<string, (IHandle, int)> candidate = handles.First();

    for (int index = 1; index < maxIndex; index++)
    {
      KeyValuePair<string, (IHandle, int)> item = handles.ElementAt(index);
    
      if(item.Value.Item2 < candidate.Value.Item2)
      {
        candidate = item;
      }          
    }
    
    candidate.Value.Item1.Dispose();
    handles.TryRemove(candidate.Key, out _);

    } }

  2. Obtenir la méthode.

    IHandle GetHandle(Dir dir, int handleOpenAttemps = 1) { if(handles.TryGetValue(dir.Name, out (IHandle, int) handle)) { handle.Item2++; } else {

    if(new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes").NextValue() < YOUR_TRESHHOLD)
    {
      FreeResources();
    }
    
    try
    {
      handle.Item1 = new Handle(dir.Name);
    }
    catch (OutOfMemoryException)
    {
    
      if (handleOpenAttemps == 2)
      {
        return null;
      }
    
      FreeResources();
      return GetHandle(dir, handleOpenAttemps++);
    }
    catch (Exception)
    {
      // Your handling.
    }
    
    handle.Item2 = 1;
    handles.TryAdd(dir.Name, handle);

    }

    return handle.Item1; }

  3. Tâche de fond.

    void SetupMemoryCheck() {

    Action<CancellationToken> BeCheckingTheMemory = ct => {

    for (;;)
    {        
      if (ct.IsCancellationRequested) break;          
    
      FreeResources();
      Thread.Sleep(500);        
    }

    };

    HostingEnvironment.QueueBackgroundWorkItem(ct => { new Task(() => BeCheckingTheMemory(ct), TaskCreationOptions.LongRunning).Start(); }); }

Si vous prévoyez une grande collection, la boucle for pourrait être optimisée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X