Endlos Backgroundworker memory problem



  • Hallo,

    ok den Op hab ich jetzt ein wenig verdrängt und die Chance gesehen ein wenig über den GC zu Diskutieren, denn der bereitet mir Regelmässig Kopfschmerzen. (Ich denke auch, das der OP hier garnicht mehr mitliest.)

    Das Thema GC lässt sich schwer Diskutieren, vor allem Anonym in einem Forum bei dem man viel geschriebenes falsch aufnehmen kann. (Und Softwareentwickler gehören ja nicht unbedingt zu den Feinfühligen Menschen 😉

    Der Mehrfachaufruf des GC:

    Der Mehrfachaufruf war aus dem Experiment heraus zu versuchen eventuell auf dem vollen Framework das gleiche Verhalten zu erzeugen wie ich es unter dem Compact Framework habe. (Zu 70% arbeite ich unter dem Compact Framework.)

    Ich wollte sicherstellen, das nicht irgendwelche Objekte überleben weil ein Objekt der Generation 0 z.B. eine Referenz hält. (Ich wirklichkeit wird das aber nichts bringen, ich war einfach zu müde für das Detail. So wird nur mehrfach Generation 0 entfernt, die Generation 2, die eventuell eine Referenz auf ein Objekt der Generation 0 hält kratzt das herzlich wenig.)

    Für mich stellt sich nach wie vor die Frage:
    - In welchem Thread läuft der GC ? (Vorallem was das Compact Framework betrifft)
    - Ist der Heap pro Appilkation oder Systemweit ?
    - Wie stellt der GC sicher, das es wegen Fragmentierung zur Speicherknappheit kommt (wenn jeder Heap Pro Applikation ist.)
    - Kann mir das verhalten der CF Anwendung im vollen Framework auch passieren ? (Dann hätte ich Grundlegend etwas falsch verstanden was die Arbeit des GCs betrifft. Und das würde mir Sorgen bereiten gegenüber allen Projekten die bisher gelaufen sind.)

    Ich finde die Dokumentation teilweise verwirrend und widersprüchlich. An sich hatte mich das auch nicht gekratzt , bis ich die oben gezeigte CF Anwendung hatte, die plötzlich mit einer Object Disposed Exception aufwarten konnte wo meinem Verständnis nach garkeine hätte sein dürfen. Nun weiß ich nicht ob dies nur auf dem CF so ist oder ich grundlegend etwas falsch verstanden habe, also das Verhalten des GCs in dem Beispiel:

    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    
    namespace SmartDeviceProject1
    {
        public partial class Form1 : Form
        {
            test te = null;
            public Form1()
            {
                InitializeComponent();
                te = new test();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                te.Main();
            }
        }
    
        public class test
        {
            Graphics gr = null;
            public void Main()
            {
                if (gr == null)
                    gr = Graphics.FromImage(new Bitmap(1, 1));
                gr.MeasureString("Hallo Welt", new Font("Arial", 12,FontStyle.Regular));
                GC.Collect(); // Aufräumen erzwingen.        }
        }
    }
    

    völlig korrekt ist. Wenn ja, hätte ich etwas falsches Gelernt und würde mir gerne durch die Community ändern. Das ist aber nicht so einfach - neigt man doch dazu schnell auf eine Dokumentation zu verweisen bzw. davon auszugehen, das sich die "Gegenseite" nicht damit beschäftigt hat. Der GC scheint dann an sich noch ein merkwürdiges Thema zu sein, mehrere Diskussionen um den GC wurden sehr schnell beendet weil einfach keiner da war, der sich intensiev damit beschäftigt hat. (Sowas wie z.B. "Was totaler Unsinn ist weil die GC der .Net-Frameworks nicht durch Speicheranforderungen ausgelöst wird"). Zugegeben, man kann nicht alles wissen und lässt auch mal falsche Informationen los weil man es nicht besser wusste, aber über Destruktive Kommentare betreffend dem Thema GC bin ich nie weit gekommen :o(

    Lange rede - kurzer Sinn:

    Die Hoffnung des ganzen lag darin, das vllt. jemand sagen kann wie sich obige Fragen verhalten. Entweder weil ich es übersehen habe (Wald vor Bäumen nicht sehen) oder einfach mehr Wissen da ist. (Ich weiß z.B. nichts von Usermode und Kernelmode , erst recht nicht wie ich von einem Sleep dahin komme. Zu wenig Ahnung der Internas (die ja vom Framework an sich versteckt werden sollen.))

    [edit] C&P Fehler beseitigt[/edit]



  • Knuddlbaer, sollten wir nicht besser einen neuen Diskussions-Thread beginnen?

    Ausgehend von Deinem Code, vermute ich, dass Du Visual Studio 2008 verwendest. Weiterhin gehe ich davon aus, dass Deine Anwendung das Compact Framework 3.5 verwendet (Voreinstellung). Sollten meine Mutmassungen zutreffen, könnte Dein Problem vielleicht gelöst werden, indem Du ein neues Projekt erstellst und gleich im ersten Dialogfenster rechts oben ".NET Framework 2.0" statt ".NET Framework 3.5" auswählst. Anschließend kannst Du Deinen Code in das neue Projekt einfügen.



  • Eventuell ist ein Moderator so nett zu Splitten, ansonsten wäre es doof wenn man jetzt mittendrinn bricht. Mit dem Originalpost hat es eh nichts mehr zu tun, aber wenn man jetzt in einem neuen Thread weiter macht geht alles verloren für all jene, die später mal darauf stoßen.

    @Mod: Ist ein Splitten ab dem ewiglangen Quotepost machbar ?

    @maro

    Es ist ein CF2.0 Projekt. 3.5 hab ich ehrlich gesagt noch nicht getestet da derzeit kein 3.5 verwendet werden darf. (Vorgabe). Das Problem des nicht Verstehens löse ich aber auch nicht mit ner Combobox 🤡



  • Oops da habe ich ja was angestossen !

    also wie schon anfangs erwähnt wurde das problem gelöst da es sich um eine endlos rekursion meinesresits gehandelt hatte. (läuft jetzt auf nem speicherlevel und räumt sich auch auf, wenn man es minimalisiert dann fällt der resourcenverbrauch sogar total runter(das war vorher nicht der fall))

    aber mal ne andere frage da ich es auch woanders gelesen habe, soll man sleep oder Thread.sleep verwenden ? oder ist es egal ?

    CU



  • Woher nimmst Du denn sleep ? Ich wüsste nicht, das C# das als Schlüsselwort anbietet. Thread.Sleep würde ich vorziehen gegenüber dem unbekannten.

    Du solltest aber wirklich überlegen ob eventuel System.Threading.Timer nicht die elegantere Lösung ist.



  • wie anfangs erwähnt ist es ein VB programm und da gibt´s eben nen windows sleep:

    http://www.vbarchiv.net/vbapi/Sleep.php
    CU



  • AFAIK kennt VB.Net kein Sleep.

    Fehler 6 Der Name "sleep" wurde nicht deklariert. Verbindung.vb 1696 13 ML_Connect2

    Und einer .Net eigenen Methode würde ich einem P/Invoke den Vorzug geben!



  • Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
    
    Sleep(10000)
    

    das geht.

    und ich werde es trotzdem mal mit Thread.sleep() probieren.
    CU



  • Ja, das ist ein P/Invoke, ein API Aufruf. Das ist keine Visual Basic Funktion.



  • und was soll man jetzt benutzen ?

    und soll man doch den GC aufrufen oder nicht ?
    (habe as mal ohne die sleeps am laufen und da läuft´s auf nem bestimmten resourcenlevel durch, ohne ständig anzuwachsen)
    CU



  • http://www.amazon.de/Microsoft-NET-Framework-Programmierung-Expertenwissen-Framework/dp/3860639846/ref=sr_1_1?ie=UTF8&s=books&qid=1205407278&sr=8-1
    Hier findet ihr detailiertes Wissen über den GC (allerdings nicht explizit für die CF Version).
    Mir persönlich gefällt das Buch sehr gut.
    Simon



  • Ähmm ich will mir jetzt kein buch deswegen kaufen !
    arbeite eh schon wieder an was anderem und das in c++.

    Gibt´s keine einfache antwort auf meine fragen ?
    THANX
    CU



  • @simon Wenn Du die Beiträge aufmerksam liest, wird Dir sicherlich auffallen, das aus diesem Buch zitiert wurde. Wenn Du dann noch das Buch liest, wirst vermutlich auch Du die Antworten vermissen. Dennoch vielen Dank für den Hinweis - das Buch kann man wirklich Empfehlen.

    @ABstraCT

    Verwende Thread.Sleep. Unterm Strich wird da vermutlich auch nur ein Aufruf auf die WinAPI Funktion Sleep sein, jedoch kann sich das jederzeit ändern. Thread.Sleep garantiert z.B., das dies auch mit Mono lauffähig ist.

    WinAPI Aufrufe aus .Net schiebt man so lange wie es geht. An sich nutzt man diese nur, wenn das Framework einen keine Möglichkeiten bietet das Problem zu lösen. Thread.Sleep ist aber eine sehr gute Lösung 🤡

    Den Garbage Collector solltest Du aufgrund des langen Blockieren des Threads einmal aufrufen. Da nicht sicher gestellt werden kann, wie der GC mit Deinem Prozess verknüpft ist, kann im schlimmsten Fall das von maro beschriebene Problem eintreten: Der GC gibt den Speicher nicht frei.

    Für Details sind Deine Informationen unzureichend so das man von dem allgemeinsten Fall ausgehen muss. (Unbekannte CPU, Workstation und eine Konfiguration für den Prozess die den GC in den Anwendungsthread presst. Und letzteres ist das Problem: Läuft der GC im Anwendungsthread wird auch dieser durch ein Sleep blockiert. Im Normalfall wird der GC aber in einem Thread parallel zur Anwendung geführt, mit niedriger Priorität.)



  • Knuddlbaer schrieb:

    Das Problem des nicht Verstehens löse ich aber auch nicht mit ner Combobox 🤡

    OK, Knuddlbaer. Hier sind meine "2 Cents":

    1. In welchem Thread läuft der GC ? (Vorallem was das Compact Framework betrifft)

    Es gibt keinen speziellen Thread für das Collecten von Objekten. Sobald der Code einen "safe point" erreicht, wird die Ausführung des Codes soz. gekappert. Alle Threads bis auf dem Thread mit dem "safe-point" werden suspendiert und der GC setzt seine Arbeit im Thread mit dem "safe-point" fort (collected aber ALLE Threads).

    Details: Beim Kompilieren der Anwendung werden sogenannte "safe points" für eine sichere Thread-Suspendierung im Code einefügt. Es sind dies Punkte wie z.B. die Rückkehr aus einem Funktions-Aufruf. Der GC hijackt den Ausführungspfad an diesen Safepoints, indem er eine andere Rücksprungadresse vom safe-point einfügt. Unter dieser Adresse befindet sich ein Aufruf der die aktuellen Threads unterbricht. Nach getaner Arbeit (Graph-Erstellung und Markierung, Kompaktierung usw.) wird wieder an die ursprüngliche Adresse gesprungen, die Thread-Suspendierung wird aufgehoben und der GC fährt mit dem Abarbeiten der finalizatin queue in einem separaten Runtime-Thread fort, während die Anwendung ihrer Wege geht.

    2. Ist der Heap pro Appilkation oder Systemweit?
    Der "managed heap" ist systemweit.

    2.1. Wie stellt der GC sicher, das es wegen Fragmentierung zur Speicherknappheit kommt.
    Indem er den Heap ständig kompaktet.

    Details: Wenn ein Prozess gestartet wird, reserviert die CLR einen kontinuierlichen, virtuellen Adressraum, dem noch kein realer Speicher zugewiesen ist (über VirtualAlloc). Ein NextObjPtr verweist am Anfang auf den Beginn des Adressraumens. Wenn ein neues Objekt auf dem Heap erstellt werden soll, wird die Größe des Objekts zur aktuellen Adresse des NextObjPtr dazuaddiert. Wenn der Heap feststellt, dass NextObjPtr außerhalb des reservierten Adressraums zeigt, ist der Heap voll und der GC muss collecten.

    3. Kann mir das verhalten der CF Anwendung im vollen Framework auch passieren ?

    Ich verstehe noch nicht ganz, was auf Deinem Gerät passiert. Es gibt eine Reihe von Unterschiede zw. CF/GC und dem "normalen" GC: Der CF-GC verwendet soviel ich weiß keine Generationen-Optimierung, auch werden große Objekte nicht anders als kleine behandelt. Im CF gibt es keine Workstation/Server-GC-Modelle. Und: Der CF-GC kann u.U. ge-jitteted Code sofort nach der Ausführung freigeben (code pitching).

    "Developers interested in the internal workings of the .NET Framework can explore this implementation of the CLI to see how garbage collection works, JIT compilation and verification is handled, security protocols implemented, and the organization of frameworks and virtual object systems."

    http://www.microsoft.com/downloads/details.aspx?FamilyId=8C09FD61-3F26-4555-AE17-3121B4F51D4D&displaylang=en



  • Die Cents sind in der falschen Währung.

    Zu 1:

    ine Garbage Collection kann durch die Common Language Runtime (CLR) gleichzeitig in einem separaten Thread oder im selben Thread wie die Anwendung ausgeführt werden

    Das hat mit den Safepoints erst mal nichts zu tun. Wie MS selbst angibt, kann der GC diese Arbeit auch im Anwendungsthread machen. Der müsste dafür ja dann doch aber Idlen - wenn er Blockiert wird (z.B. WaitHandle) dürfte auch der GC Blockiert sein.

    Zu 2:

    Wenn der Heap Systemweit komprimiert wird, würde dies bedeuten, das die Threads aller .Net Anwendungen angehalten werden müssen. Ich würde dann auch keinen Sinn darin sehen, das der GC in den Anwendungsthread gepackt werden kann wenn doch ohnehin irgendwo ein globaler GC überwachen muss.

    Denkbar ist, das der Heap Systemweit ist , jede Anwendung ein Stück des Heaps zugewiesen und reserviert bekommt und global über die Wurzel verwaltet wird. Dazu wäre dann aber auch ein Systemweiter GC nötig ?

    [ großer globaler Heap von dem jeder etwas abbekommt]
    [Anwendung A][Anwendung B][Anwendung C][Anwendung D]
    

    So, nun braucht Anwendung A mehr Platz, B und C D sind gut Optimiert. Was passiert nun ?

    [ großer globaler Heap von dem jeder etwas abbekommt                       ]
                 [Anwendung B][Anwendung C][Anwendung D][Anwendung A           ]
    

    Was passiert wenn C komplett wegfällt ?

    [ großer globaler Heap von dem jeder etwas abbekommt                       ]
                 [Anwendung B]             [Anwendung D][Anwendung A           ]
    

    Somit wäre der Heap Fragmentiert, A D und B für sich vllt. nicht. Damit nun der gesamte Heap defragmentiert werden kann müssen alle .Net Threads angehalten werden. Besonders teuer wären dann .Net Anwendungen die P/Invoke aufrufe machen, die können im Nativecode nicht angehalten werden.

    Und hier fehlt mir jetzt in der Dokumentation der Hinweis, was genau passiert. Werden wirklich alle .Net Threads Systemweit angehalten ? (Ich wüsste nicht, wie der GC diesen sonst komprimieren soll.)

    Zu 3.
    Was auf dem Gerät passiert kannst Du ausprobieren - der Emulator geht. Wenn Du Dir das Beisiel anschaust. Was würdest Du sagen - beim 2. Klick auf den Button eine Exception oder keine ?

    Thx für den Link, wird mich eine weile beschäftigen.



  • Eine Garbage Collection kann durch die Common Language Runtime (CLR) gleichzeitig in einem separaten Thread oder im selben Thread wie die Anwendung ausgeführt werden

    Lies doch mal bitte den ersten Punkt bis ans Ende ("...in einem separaten Runtime-Thread").

    Das hat mit den Safepoints erst mal nichts zu tun. Wie MS selbst angibt, kann der GC diese Arbeit auch im Anwendungsthread machen. Der müsste dafür ja dann doch aber Idlen - wenn er Blockiert wird (z.B. WaitHandle) dürfte auch der GC Blockiert sein.

    Hmmm... Ich würde mich fragen: Woher weiss der GC, wann es die Ausführung eines Threads gefahrlos unterbrechen kann?

    Zu 2:

    Das ist reine Spekulation (auch meinerseits).

    Zu 3.

    Was auf dem Gerät passiert kannst Du ausprobieren - der Emulator geht. Wenn Du Dir das Beisiel anschaust. Was würdest Du sagen - beim 2. Klick auf den Button eine Exception oder keine ?

    Wie wär's denn mit:

    public class test 
        { 
            public void Main() 
            {
                Graphics gr = null; 
    
                if (gr == null) 
                    gr = Graphics.FromImage(new Bitmap(1, 1)); 
                gr.MeasureString("Hallo Welt", new Font("Arial", 12,FontStyle.Regular));
                gr.Dispose();
    
                GC.Collect(); // Aufräumen erzwingen.        
                GC.WaitForPendingFinalizers();
                GC.Collect();
            } 
        }
    

    Ich guck mir Dein Beispiel noch an...



  • Ok, nun versperrt mit der Baum die Sicht auf den Wald.

    Wo hast Du das mit dem "Runtime-Thread" her ? Kann ich nicht finden. Das was Du schreibst ist der Bereich, der die Finalizer aufruft, nicht der, der die Objekte in die Queue für den Finalizer Thread einreiht.

    (collected aber ALLE Threads)

    Und darum geht es ja. Wenn das ganze Global ist, müsste er alle Treads aller .Net Anwendungen die gerade laufen unterbrechen.

    Das "Global arbeiten" hat aber auch Auswirkungen auf das "Timer -> 48h warten -> kein Collect"

    (Ich denke mal, in diesem Punkt wird man nie auf ein Ergebnis kommen. Das dürfte sowas von Implementierungsabhängig sein - leider mit vielen direkten Auswirkungen.)

    Naja, zum Spekulieren muss man nicht immer an die Börse, dafür gibt es hier keine Steuern 🤡

    Das was Du zeigst ist der Workarround. Leider ist das Objekt, was im real code erzeugt wird Teuer. Und ich hätte es gerne pro Instanz nur einmal angelegt.



  • Knuddlbaer schrieb:

    Wo hast Du das mit dem "Runtime-Thread" her ? Kann ich nicht finden. Das was Du schreibst ist der Bereich, der die Finalizer aufruft, nicht der, der die Objekte in die Queue für den Finalizer Thread einreiht.

    J. Richter - Garbage Collection: Automatic Memory Management in the .NET Framework schrieb:

    There is a special runtime thread dedicated to calling Finalize methods. When the freachable queue is empty (which is usually the case), this thread sleeps. But when entries appear, this thread wakes, removes each entry from the queue, and calls each object's Finalize method.

    http://msdn.microsoft.com/library/default.asp?url=/msdnmag/issues/1100/gci/toc.asp

    (collected aber ALLE Threads)

    Ich meinte hier alle Threads einer laufenden Anwendung.

    Das was Du zeigst ist der Workarround. Leider ist das Objekt, was im real code erzeugt wird Teuer. Und ich hätte es gerne pro Instanz nur einmal angelegt.

    Wer hindert Dich daran?

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    
    namespace SmartDeviceProject2
    {
        public partial class Form1 : Form 
        {
            Graphics gr;
            test te = null;
    
            public Form1() 
            { 
                InitializeComponent(); 
                te = new test(ref gr); 
            } 
    
            private void button1_Click(object sender, EventArgs e) 
            { 
                te.Main();
            } 
        } 
    
        public class test 
        {
            Graphics gr;
    
            public test(ref Graphics Gr)
            {
                this.gr = Gr;
            }
    
            public void Main() 
            {
                if (gr == null) 
                    gr = Graphics.FromImage(new Bitmap(1, 1));
    
                gr.MeasureString("Hallo Welt", new Font("Arial", 12, FontStyle.Regular));
            } 
        } 
    }
    

    Oder die "kurze" Lösung:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    
    namespace SmartDeviceProject2
    {
        public partial class Form1 : Form 
        {
            test te = null;
    
            public Form1() 
            { 
                InitializeComponent(); 
                te = new test(); 
            } 
    
            private void button1_Click(object sender, EventArgs e) 
            { 
                te.Main();
            }
        } 
    
        public class test
        {
            Graphics gr;
    
            public void Main() 
            {
                if (gr == null) 
                    gr = Graphics.FromImage(new Bitmap(1, 1));
    
                gr.MeasureString("Hallo Welt", new Font("Arial", 12, FontStyle.Regular));
                GC.KeepAlive(gr);
            }
        }
    

    Oder die "saubere" Lösung:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    
    namespace SmartDeviceProject2
    {
        public partial class Form1 : Form 
        {
            test te = null;
    
            public Form1() 
            { 
                InitializeComponent(); 
                te = new test(); 
            } 
    
            private void button1_Click(object sender, EventArgs e) 
            { 
                te.Main();
            }
        } 
    
        public class test : IDisposable
        {
            Graphics gr;
    
            public void Main() 
            {
                if (gr == null) 
                    gr = Graphics.FromImage(new Bitmap(1, 1));
    
                gr.MeasureString("Hallo Welt", new Font("Arial", 12, FontStyle.Regular));
            }
    
            #region IDisposable Member
    
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
    
            protected virtual void Dispose(bool disposing)
            {
                if (disposing)
                {
                    if (gr != null)
                        gr.Dispose();
                }
            }
    
            #endregion
        } 
    }
    

    J.Richter in Cwalina & al. - Framework Design Guidelines, S. 253 schrieb:

    The idea here is that Dispose(Boolean) knows whether it is being called to do explicit cleanup (the Boolean is true) versus being called due to a garbage collection (the Boolean is false). This distinction is useful because, when being disposed explicitly, the Dispose(Boolean) method can safely execute code using reference type fields that refer to other objects knowing for sure that these other objects have not been finalized. When the boolean is false, the Dispose(Boolean) method should not execute code that refers to reference type fields because those objects might have already been finalized.

    Hoffe, dass Dir das weiterhilft.



  • Na, dann aber bitte richtig lesen 🤡

    There is a special runtime thread dedicated to calling Finalize methods. When the freachable queue is empty (which is usually the case), this thread sleeps. But when entries appear, this thread wakes, removes each entry from the queue, and calls each object's Finalize method.

    Es geht hier um den Thread, der die Objekte in die freachable Queue steckt.

    [quote]
    (collected aber ALLE Threads)

    Ich meinte hier alle Threads einer laufenden Anwendung. [quote]

    Das steht aber ein wenig mit den anderen Aussagen in Konflikt: Systemweiter Heap der Komprimiert wird. (Aber hier wird der SSCLI Code vermutlich helfen, es wird nur ne weile dauern bis ich den durch hab.)

    Das SupressFinalize ist eine nette Idee, funktioniert leider nicht.
    http://www.rothmichael.de/cpp/GC-CF20.swf (CF20 SP1)

    Unter CF3.5 geht es wie erwartet. Also muss ich mich nicht mehr so lange mit den "Merkwürdigkeiten" beschäftigen.

    Btw.: Was bei dem Problem noch hilft das das Pinnen des Objektes (die Variante die noch verschoben werden darf.)



  • Knuddlbaer schrieb:

    Na, dann aber bitte richtig lesen 🤡

    Yepp. Da hab ich mich wohl falsch ausgedrückt, gemeint war die freachable queue. Wenn Du Informationen über einen separaten Thread hast, "der die Objekte in die freachable Queue steckt", nur her damit...! Mein Wissen ist z.Z. dass dies innerhalb des ge-hijackten und suspendierten Threads geschieht.

    Das SupressFinalize ist eine nette Idee, funktioniert leider nicht.
    http://www.rothmichael.de/cpp/GC-CF20.swf (CF20 SP1)

    Lass mal GC.Collect() weg, dann wird's schon tun. Hab ich da "Danke" gehört?

    Und falls Du mit GC.Collect() einen GC-Lauf nur als Test für die Solidität Deiner Klasse erzwingen wolltest (Konstruktion ist teuer usw.), dann solltest Du lieber die Klasse vorher abändern:

    public class test
        {
            Graphics gr = null;
            Bitmap bm = null;
    
            public void Main() 
            {
                if (gr == null)
                {
                    if (bm == null)
                        bm = new Bitmap(1, 1);
    
                    gr = Graphics.FromImage(bm);
                }
    
                gr.MeasureString("Hallo Welt", new Font("Arial", 12, FontStyle.Regular));
                GC.Collect();
            }
        }
    

    Na sowas...


Anmelden zum Antworten