Prozessspeicher steigt bei SoundPlayer

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 4 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Prozessspeicher steigt bei SoundPlayer

    Hallo,

    nach langer Zeit bin ich mal wieder hier mit einem Problem. Ich habe eine Funktion playSound(string soundNum) die einfach aus einem dictionary mit dem übergebenen Key einen Stream zum abspielen rausholt. Die .wav Dateien sind als Ressourcen in das Projekt eingebunden. Das funktioniert auch ziemlich, nur wird jedes mal wenn diese Funktion aufgerufen wird, der Prozessspeicher erhöt, und das stört mich. Irgendetwas muss ich grundlegend falsch verstanden haben, weil auch nach ende der Methode sterben die Objekte darin nicht richtig. Wenn ich z.b. am ende ein s.Dispose() hab, bekomm ich beim nächsten mal aufrufen der Funktion mit gleichem Stream ein System.ObjectDisposedException obwohl der stream neu gesetzt wird. ?( ?( ?(

    Testweise hab ich den Funktionsaufruf im Main in einer while(true) schleife.

    Spoiler anzeigen

    C#-Quellcode

    1. static void Main(string[] args) {
    2. while(true){
    3. playSound("1");
    4. }
    5. }
    6. public static void playSound(string soundNum) {
    7. SoundPlayer player = new SoundPlayer();
    8. Stream s = null;
    9. try { soundDic.TryGetValue(Int16.Parse(soundNum), out s); } catch(Exception e) { Console.WriteLine(e.GetType().ToString() + ": Can't find Sound!"); }
    10. player.Stream = s;
    11. s.Position = 0;
    12. if(!s.Equals(null)) { player.Play(); } else { SystemSounds.Asterisk.Play(); }
    13. Thread.Sleep(5000);
    14. player.Dispose();
    15. s.Dispose();
    16. }



    Eventuell kann mir ja einer von euch erklären, wieso sich der Speicher bei jedem Funktionsaufruf erhöht, danke!
    LG Quellcoder
    Projekte
    Was deine Exception angeht, ist das Problem realtiv einfach.

    Zu aller erst, Stream, wie fast alle anderen Klassen in .NET, ist ein Referenztyp. Dies bedeutet, dass du, wenn du diesen Stream einer Variable zuweist, diese Variable eine Referenz auf das eigentliche Objekt enthält. In dem Moment in dem du deiner Variable s den Stream aus dem Dictionary zuweist, hast du nun in deinem Dictionary, und in der Variablen s einen Verweis auf ein und denselben Stream. Wenn du nun am Ende der Methode s.Dispose() aufrufst, rufst du auf das zugrundeliegende Objekt Dispose auf, wodurch, wenn du es wieder verwenden möchtest, natürlich die ObjectDisposedException fliegt. Du hast das Objekt ja disposed, weswegen du es nicht weiter verwenden darfst.

    Was den verwendeten Arbeitsspeicher angeht, das ist ein bei weitem kniffligeres Thema. .NET hat einen GarbageCollector (GC). Dieser ist dazu da, Speicher der nicht mehr benötigt wird wieder freizugeben, sodass wir entwickler uns (fast) nicht mehr darum kümmern müssen. Damit jedoch Speicher eingesammelt werden kann, muss das Programm angehalten werden, die Objekte und deren Referenzen überprüft, sortiert, und dann eventuell freigegeben werden. Erst dann läuft das Programm weiter. Um zu verhindern, dass das Programm dadurch gebremst, oder gar sichtbar angehalten wird, läuft der GC so oft wie nötig, so selten wie möglich. Und daraus ergibt sich auch eine Faustregel die man sich was den RAM verbrauch des Programmes angeht merken kann. Je mehr RAM das System zur verfügung hat, desto seltener wird der GC aktiv. Es ist keine seltenheit, dass ein Programm von mehreren hundert MB an RAM auf unter hundert MB springt, wenn der GC mal dazu genötigt war aufzuräumen. Auf einem System, das wenig RAM hat, wird der GC wesentlich öfters anspringen und versuchen Speicher für das System freizugeben.

    Einen Tipp was .Dispose() angeht möchte ich dir noch geben:
    Wenn eine Klasse IDisposable Implementiert, kannst du ein Objekt dieser Klasse in einen Using-Block packen, sodass automatisch Dispose() aufgerufen wird.
    In deinem Fall sähe dass dann so aus:

    C#-Quellcode

    1. public static void playSound(string soundNum)
    2. {
    3. if (soundDic.TryGetValue(Int16.Parse(soundNum), out Stream s))
    4. {
    5. using (SoundPlayer player = new SoundPlayer())
    6. {
    7. player.Stream = s;
    8. s.Position = 0;
    9. player.Play();
    10. }
    11. }
    12. else
    13. {
    14. SystemSounds.Asterisk.Play();
    15. Console.WriteLine($"Can't find Sound for soundNum:{soundNum}");
    16. }
    17. Thread.Sleep(5000);
    18. }

    Ich habe mir die Freiheit genommen, den Code mal zu "Entpacken" und zu "säubern", oder um es anders zu sagen, ich hab meine persönlichen Präferenzen auf deinen Code angewandt. Ich schätze da werden ein paar Dinge drin sein, die dir noch unbekannt sind. Beispielsweise die Inlinedeklaration von Variablen in Methoden mit out Parametern. Dadurch spart man sich die Zeile um die Variable zu deklarieren. Zusätzlich habe ich die Nullprüfung, und das Try-catch entfernen können, da wir ja nur einen SoundPlayer benötigen, wenn wir auch tatsächlich einen Stream haben. Allein dadurch sparen wir uns schon die Erstellung eines unnötigen Objektes. Auch habe ich mal das s.Dispose() entfernt, da du ja die Sounds wohl mehr als nur einmal abspielen möchtest.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „EaranMaleasi“ ()

    @Quellcoder02 Warum konvertierst Du zwischen Strings ("1") und numerischen Werten (1) hin und her?
    Übergib der Prozedur den Integer-Wert und feddich.
    Dann würde ich den Player während der Laufzeit in einer Klassen-Instanz anlegen und ihm bei Bedarf einen neuen Sound zuweisen und ggf. fragen, ob er schon fertig ist.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @EaranMaleasi Danke für deine Antwort. Ich hab ganz vergessen, dass in .NET fast alles ein Referenztyp ist, danke jetzt hab ichs auch verstanden warum diese Exception kommt. Das mit dem Garbage Collector habe ich weitesgehend auch gewusst, nur muss der doch irgendwann mal anspringen wenn schon so viel Speicher belegt ist. Ich habe auch einmal gehört, dass manuell anwerfen auch nichts bringen soll, weil er selber entscheidet ob er den Thread jetzt anhält und säubert oder einfach nichts macht. Im richtigen Programm wird die Methode von einem Listener von einem anderen Thread aufgerufen. Gibt es irgendeine Methode ihm zu sagen, dass er den Thread zwangsweise anhalten und aufräumen soll?

    Danke,
    LG Quellcoder02
    Projekte
    @Quellcoder02 Nicht aufräumen, sondern das Aufräumen nicht erforderlich machen!
    Pack die Player-Instanz aus der Prozedur in die Klasse und dispose sie bei Beendigung des Programms.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!