2016-07-01 17 views
1

Ich versuche zu testen, wie die schweren Objekte innerhalb der .net BlockingCollection behandelt werden.Garbage Collector kann Speicherressourcen nicht wiederherstellen

Ich bekomme die schweren Objekte mit einigen API, und möchte es in mehreren Threads verarbeiten. Um dies zu tun, habe ich einen Thread, der die schweren Objekte liest und sie in mehrere Threads verschiebt, wobei jeder Thread seine eigene blockierende Sammlung hat und jeder dieser Threads das Objekt aus der Sammlung und dem Prozess herausnimmt. Ich erwarte, dass GC, wenn das Objekt aus der gesamten Sammlung entfernt wird, wo immer es sitzt, in der Lage sein sollte, es zu säubern. Das Aufräumen passiert nicht und mein Programm geht aus dem Speicher.

Calling GC.Collect() dazwischen hilft mir, den Prozess abzuschließen, aber es hat erhebliche Leistungseinbußen, die ich mir nicht leisten kann zu nehmen.

Meine einzige Frage ist, warum Garbage Collector die Ressourcen hier nicht freigeben kann, auch wenn die Objekte nicht in Reichweite sind.

public class DummyProcessor 
{ 
    List<BlockingCollection<object>> listOfBlockingCollection = null; 

    void ProcessCollection(object blockingCollection) 
    { 
     BlockingCollection<object> collection = (BlockingCollection<object>)blockingCollection; 

     while (collection.IsCompleted == false) 
     { 
      object heavyObject = collection.Take(); 

      CallExternalProcess(heavyObject); 
     } 
    } 

    private void CallExternalProcess(object heavyObject) 
    { 
     throw new NotImplementedException(); 
    } 

    public void Analyze(object heavyObject) 
    { 
     if (listOfBlockingCollection == null) 
     { 
      listOfBlockingCollection = new List<BlockingCollection<object>>(); 

      for (int i = 0; i < 25; i++) 
      { 
       BlockingCollection<object> coll = new BlockingCollection<object>(); 

       listOfBlockingCollection.Add(coll); 

       Thread pt = new Thread(new ParameterizedThreadStart(ProcessCollection)); 

       pt.Start(coll); 
      } 
     } 

     for (int i = 0; i < 25; i++) 
     { 
      listOfBlockingCollection[i].Add(heavyObject); 
     } 
    } 
} 
+0

Sie sagten 'GC.Collect()' macht es funktioniert, das bedeutet, dass der Garbage Collector die Elemente sammeln kann, ist es nur nicht zu wählen. Haben Sie versucht, einen Speicher-Profiler auf dem Programm auszuführen, bis zu dem Punkt, an dem er mit einem Speicherfehler abstürzt? Vielleicht sind die Objekte nicht außerhalb des Geltungsbereichs, wenn Sie denken, dass sie es sind. Sie fügen allen 25 Sammlungen dasselbe Objekt hinzu. Wenn einer der Threads langsam ausgeführt wird, können alle Objekte, die noch bearbeitet werden müssen, nicht gesammelt werden. Ich bin immer noch neugierig, warum Sie 25 Sammlungen verwenden, die bewirken, dass das gleiche Objekt 25 Mal bearbeitet wird. –

+0

@ScottChamberlain Visual Studio Speicher Profiler läuft schrecklich langsam, wahrscheinlich muss ich es über Nacht laufen lassen. Aber ich bin mir sicher, dass diese Objekte nicht veröffentlicht werden, auch wenn sie aus der Sammlung entfernt wurden. Der Code, den ich zur Verfügung gestellt habe, ist der Überblick darüber, was im Originalcode passiert. Dasselbe Objekt wird zu mehreren Sammlungen hinzugefügt, da jede Sammlung auf ihre eigene Weise verarbeitet und in ihrem eigenen Format in einen anderen Datenspeicher geschrieben wird. – Bikswan

+0

Ich bin ein großer Fan von [dotMemory] (https://www.jetbrains.com/dotmemory/), es kommt mit Resharper, wenn Sie es haben, oder es hat auch eine kostenlose 5-Tage-Testversion, wenn Sie dies nicht tun. –

Antwort

1

Nachdem ich die Anwendung über dotMemory ausgeführt hatte, stellte ich fest, dass die schweren Objekte zu schnell erzeugt wurden, dass sie in großer Anzahl über den Computerspeicher hinaus angesammelt wurden.
Mein ausdrücklicher Aufruf an GC.Collect funktionierte nicht, weil es das verwendete schwere Objekt freigab, aber es fügte Art der Pause zur schweren Gegenstandproduktion ein, die Verbrauchergewinnen mehr Zeit gab, um zu klären, was bereits im BlockingCollection ist.
Also musste ich warten zwischen dem schweren Objekt Hersteller und Verbraucher, so dass ich nicht den verfügbaren Speicher überbeanspruchen.Dafür benutze ich die AutoResetEvent. Ich rufe AutoResetEvent.WaitOne an, nachdem die Sammlungsgröße etwas Reichweite erreicht und sobald es unter den Bereich fällt, rufe ich AutoResetEvent.Set an, um den Produzenten wieder zu laufen.

Vielen Dank für Ihre Eingabe zu diesem Thema.

2

Im Moment kann ich keine Bedrohungen in diesem Code sehen, wenn die Analyze in Thread-sichere Weise aufgerufen wird. Wenn es eine Analyse aus dem dotMemory gibt, fügen Sie sie bitte zur Frage hinzu.

Allerdings möchte ich darauf hinweisen, wie Sie die Analyze Methode aufrufen. Zunächst initialisieren Sie listOfBlockingCollection Variable in nicht Thread-sicherer Weise, denn wenn Sie eine zwei Threads ausführen die Analyze Methode, können Sie das Rennen und eine Situation, in der mehr als ein Thread führt die if Klausel in Ihrem Code .

In diesem Fall erstellen Sie mindestens 25 Geister Threads und mehr signifikant, können Sie die Situation dann listOfBlockingCollection haben mehr als 25 Artikel bekommen: nach wie vor, wenn einige Thread bereits aus if-Klausel und andere erstellt Threads und fügt Sammlungen zur Liste hinzu (Sie können zum Beispiel die listOfBlockingCollection.Count auschecken). Ein Thread ist mindestens 2 MB im Speicher und als IDisposable Objekt wird er nicht so schnell wie gewünscht gesammelt.

Andere Problem mit Ihrem Code ist, dass 25 Threads eine nicht effiziente Anzahl von Threads ist (nur wenn Sie einen Supercomputer mit 32 Kernen haben, könnte dies eine Option sein) wegen der Kontextwechsel. Es ist besser, die Anzahl der Threads zu verwenden, die der Anzahl der Kerne in Ihrem System entspricht, oder, besser, wechseln Sie Ihren Code auf Task -orientiert (Sie können problemlos 25 Tasks für die Verarbeitung erstellen und in ihnen eine Schleife einfügen).

Eine weitere Option, wenn Sie einen zusätzlichen Daten-Workflow benötigen, können Sie in Ihrer Anwendung eine TPL DataFlow-Bibliothek mit 25 ActionBlocks verwenden, die 25 verschiedene Flüsse startet. Nachdem Sie die schwere Objektwarteschlange beendet haben, können Sie ganz einfach send the Complete command über die Blöcke hinweg ausführen und Ihre Ausführung beenden.

+0

Danke. Ja, ich muss die Analyze-Methode threadsicher aufrufen. Aber der Code, den ich hier eingefügt habe, ist das Abbild dessen, was ich im realen Projekt mache. Das tut mir leid. – Bikswan