Vraag Correct gebruik van de IDisposable interface


Ik weet het van het lezen de MSDN-documentatie dat het "primaire" gebruik van de IDisposable interface is om onbeheerde bronnen op te schonen.

Voor mij betekent "onbeheerd" zaken als databaseverbindingen, contactdozen, vensterhandvatten, enz. Maar ik heb code gezien waar de Dispose() methode is geïmplementeerd om te bevrijden beheerd middelen, wat mij overbodig lijkt, omdat de vuilnisman dat voor je moet regelen.

Bijvoorbeeld:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Mijn vraag is, maakt dit de garbage collector gratis geheugen gebruikt door MyCollection sneller dan normaal?

Bewerk: Tot nu toe hebben mensen enkele goede voorbeelden gepost van het gebruik van IDisposable om niet-beheerde bronnen zoals databaseverbindingen en bitmaps op te schonen. Maar veronderstel dat _theList in de bovenstaande code bevatte een miljoen strings, en je wilde die herinnering vrijmaken nu, in plaats van te wachten op de vuilnisman. Zou de bovenstaande code dat bereiken?


1378
2018-02-11 18:12


oorsprong


antwoorden:


Het punt van Dispose is om onbeheerde bronnen vrij te maken. Het moet op een gegeven moment worden gedaan, anders worden ze nooit opgeruimd. De vuilnisman weet het niet hoe bellen DeleteHandle() op een variabele van het type IntPtr, het weet het niet of of niet, het moet bellen DeleteHandle().

Notitie: Wat is een onbeheerde bron? Als u het in het Microsoft .NET Framework hebt gevonden: het is beheerd. Als je zelf met MSDN rondneusde, is het niet beheerd. Alles wat je hebt gebruikt P / Invoke-oproepen om buiten de prettige comfortabele wereld te geraken van alles wat beschikbaar is voor jou in .NET Framwork is onbeheerd - en je bent nu verantwoordelijk voor het opschonen.

Het object dat je hebt gemaakt, moet worden ontmaskerd sommige methode, die de buitenwereld kan bellen, om onbeheerde bronnen op te schonen. De methode kan worden genoemd wat je maar wilt:

public void Cleanup()

public void Shutdown()

Maar in plaats daarvan is er een gestandaardiseerde naam voor deze methode:

public void Dispose()

Er is zelfs een interface gemaakt, IDisposable, dat heeft precies die ene methode:

public interface IDisposable
{
   void Dispose()
}

Dus je laat je object de IDisposable interface, en op die manier beloof je dat je die ene methode hebt geschreven om je niet-beheerde bronnen op te schonen:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

En je bent klaar. Behalve dat je beter kunt doen.


Wat als uw object een 250 MB heeft toegewezen? System.Drawing.Bitmap (d.w.z. de .NET beheerde Bitmap-klasse) als een soort framebuffer? Natuurlijk, dit is een beheerd .NET-object en de garbagecollector zal het vrijmaken. Maar wil je echt 250MB geheugen achterlaten, gewoon daar zitten wachten tot de vuilnisman tenslotte kom langs en bevrijd het? Wat als er een is open databaseverbinding? We willen toch niet dat die verbinding open blijft staan, wachtend tot de GC het object afrondt.

Als de gebruiker heeft gebeld Dispose() (wat betekent dat ze niet langer van plan zijn om het object te gebruiken) waarom niet van die verspillende bitmaps en databaseverbindingen ontdoen?

Dus nu zullen we:

  • zich ontdoen van onbeheerde bronnen (omdat we dat moeten doen), en
  • ontdoen van beheerde bronnen (omdat we nuttig willen zijn)

Dus laten we onze updaten Dispose() methode om die beheerde objecten te verwijderen:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

En alles is goed, behalve dat je beter kunt doen!


Wat als de persoon vergeten bellen Dispose() op je object? Dan lekten ze wat unmanaged middelen!

Notitie: Ze zullen niet lekken beheerd middelen, omdat uiteindelijk de garbage collector wordt uitgevoerd, op een achtergrondthread en het geheugen vrijmaakt dat is gekoppeld aan ongebruikte objecten. Dit omvat uw object en alle beheerde objecten die u gebruikt (bijvoorbeeld de Bitmap en de DbConnection).

Als de persoon vergat te bellen Dispose(), we kunnen nog steeds red hun spek! We hebben nog steeds een manier om het te noemen voor hen: wanneer de garbage collector eindelijk rondgaat om ons object te bevrijden (dat wil zeggen ons te finaliseren).

Notitie: De garbage collector zal uiteindelijk alle beheerde objecten bevrijden.   Wanneer dit het geval is, wordt het Finalize   methode op het object. De GC weet het niet, of   geven om jouw  Gooi methode.   Dat was maar een naam waar we voor kozen   een methode die we noemen wanneer we willen krijgen   ontdoen van onbeheerde dingen.

De vernietiging van ons object door de vuilnisman is het perfect tijd om die vervelende niet-beheerde bronnen te bevrijden. We doen dit door het Finalize() methode.

Notitie: In C # overschrijf je niet expliciet de Finalize() methode.   Jij schrijft een methode die lijkt op een C ++ destructor, en de   compiler neemt dat als uw implementatie van de Finalize() methode:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Maar er zit een fout in die code. Zie je, de garbage collector draait op a achtergronddraad; je kent de volgorde waarin twee objecten zijn vernietigd niet. Het is heel goed mogelijk dat je in je Dispose() code, de beheerd object waarvan je probeert af te raken (omdat je nuttig wilde zijn) is er niet meer:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Dus wat je nodig hebt, is een manier voor Finalize() vertellen Dispose() dat het zou moeten Raak geen beheerd aan middelen (omdat ze misschien niet daar meer), terwijl nog steeds onbeheerde bronnen vrijgemaakt worden.

Het standaardpatroon om dit te doen is te hebben Finalize() en Dispose() beide bellen een derde(!) methode; waar je een Booleaans gezegde doorgeeft als je het oproept Dispose() (in tegenstelling tot Finalize()), wat betekent dat het veilig is voor gratis beheerde bronnen.

Deze intern methode kon een willekeurige naam krijgen zoals "CoreDispose", of "MyInternalDispose", maar het is traditie om het te noemen Dispose(Boolean):

protected void Dispose(Boolean disposing)

Maar een meer nuttige parameternaam kan zijn:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

En je verandert je implementatie van de IDisposable.Dispose() methode om:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

en je finalizer om:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Notitie: Als uw object afstamt van een object dat wordt geïmplementeerd Dispose, vergeet dan niet om hun te bellen baseren Werp de methode weg wanneer u overschrijft Verwijder:

public Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

En alles is goed, behalve dat je beter kunt doen!


Als de gebruiker belt Dispose() op je object, dan is alles opgeruimd. Later, wanneer de garbage collector langskomt en Finalize belt, zal hij dan bellen Dispose nog een keer.

Dit is niet alleen verspilling, maar als je object ongewenste verwijzingen bevat naar objecten die je al hebt verwijderd van de laatste bellen naar Dispose(), je zult proberen ze weer weg te gooien!

In mijn code merk ik dat ik zorgvuldig verwijzingen naar objecten verwijderde die ik heb afgehandeld, dus ik probeer niet te bellen Dispose op een junk-objectverwijzing. Maar dat weerhield een subtiele bug er niet van om in te kruipen.

Wanneer de gebruiker belt Dispose(): het handvat CursorFileBitmapIconServiceHandle is vernietigd. Later, wanneer de garbage collector loopt, zal het proberen hetzelfde handvat opnieuw te vernietigen.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

De manier waarop je dit oplost, vertelt de garbagecollection dat het niet nodig is om het object te finaliseren - de resources zijn al opgeruimd en er is geen werk meer nodig. Je doet dit door te bellen GC.SuppressFinalize() in de Dispose() methode:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Nu heeft de gebruiker gebeld Dispose(), we hebben:

  • bevrijd onbeheerd bronnen
  • bevrijd beheerde bronnen

Het heeft geen zin in de GC die de finale draait - alles is geregeld.

Kon ik Finalize niet gebruiken om niet-beheerde bronnen op te schonen?

De documentatie voor Object.Finalize zegt:

De methode Voltooien wordt gebruikt om opschoningsbewerkingen uit te voeren op niet-beheerde bronnen die door het huidige object worden vastgehouden voordat het object wordt vernietigd.

Maar de MSDN-documentatie zegt ook, voor IDisposable.Dispose:

Voert toepassingsspecifieke taken uit die zijn gekoppeld aan het vrijmaken, vrijgeven of opnieuw instellen van niet-beheerde bronnen.

Dus wat is het? Op welke plek moet ik onbeheerde bronnen opruimen? Het antwoord is:

Het is jouw keuze! Maar kies Dispose.

Je zou zeker je onbeheerde opruiming in de finale kunnen plaatsen:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

Het probleem daarmee is dat je geen idee hebt wanneer de garbagecollector je object gaat afwerken. Uw niet-beheerde, niet-benodigde, niet-gebruikte native bronnen blijven rond tot de vuilnisman tenslotte runs. Dan zal het je finalizer-methode noemen; opruimen van onbeheerde bronnen. De documentatie van Object.Finalize wijst dit:

Het exacte tijdstip waarop de finalizer wordt uitgevoerd, is niet gedefinieerd. Om te zorgen voor deterministische vrijgave van bronnen voor instanties van uw klas, implementeer a Dichtbij methode of geef een IDisposable.Dispose implementatie.

Dit is de deugd van het gebruik Dispose om onbeheerde bronnen op te schonen; u leert kennen en beheersen wanneer onbeheerde bronnen worden opgeruimd. Hun vernietiging is "Deterministische".


Om je oorspronkelijke vraag te beantwoorden: waarom zou je het geheugen nu niet vrijgeven, in plaats van wanneer de GC besluit het te doen? Ik heb een gezichtsherkenningssoftware die behoefte aan om 530 MB interne afbeeldingen kwijt te raken nu, omdat ze niet langer nodig zijn. Wanneer we dat niet doen: de machine maalt naar een verwisselmoment.

Bonus lezen

Voor iedereen die van de stijl van dit antwoord houdt (uitleg van de waarom, dus de hoe wordt duidelijk), stel ik voor dat je hoofdstuk een van de Essentiële COM van Don Box leest:

In 35 pagina's legt hij de problemen van het gebruik van binaire objecten uit, en bedenkt COM voor je ogen. Als je eenmaal de waarom van COM, de resterende 300 pagina's liggen voor de hand, en geven alleen de implementatie van Microsoft weer.

Ik denk dat elke programmeur die ooit met objecten of COM heeft gewerkt, op zijn minst het eerste hoofdstuk moet lezen. Het is de beste verklaring ooit.

Extra Bonus Reading

Als alles wat je weet fout is door Eric Lippert

Het is daarom erg moeilijk om een ​​correcte finalizer te schrijven,   en het beste advies dat ik je kan geven is om het niet te proberen.


2286
2018-02-11 18:20



IDisposable wordt vaak gebruikt om het using verklaring en profiteer van een eenvoudige manier om deterministische opschoning van beheerde objecten te doen.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

54
2018-02-11 18:42



Het doel van het Dispose-patroon is om een ​​mechanisme te bieden voor het opschonen van zowel beheerde als onbeheerde bronnen en wanneer dat gebeurt, hangt dit af van hoe de Dispose-methode wordt aangeroepen. In uw voorbeeld is het gebruik van Dispose eigenlijk niets dat te maken heeft met dispose, omdat het wissen van een lijst geen invloed heeft op het weggooien van die verzameling. Evenzo hebben de aanroepen om de variabelen op null in te stellen geen invloed op de GC.

U kunt dit bekijken artikel voor meer details over het implementeren van het Dispose-patroon, maar in principe ziet het er als volgt uit:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

De methode die hier het belangrijkst is, is de Dispose (bool), die eigenlijk onder twee verschillende omstandigheden draait:

  • disposing == true: de methode is direct of indirect aangeroepen door de code van een gebruiker. Beheerde en onbeheerde bronnen kunnen worden verwijderd.
  • disposing == false: de methode is door de runtime binnen de finalizer aangeroepen en je moet niet naar andere objecten verwijzen. Alleen onbeheerde bronnen kunnen worden verwijderd.

Het probleem met het eenvoudig laten werken van de GC voor het opschonen, is dat je geen echte controle hebt over wanneer de GC een verzamelcyclus zal uitvoeren (je kunt GC.Collect () aanroepen, maar dat zou je echt niet moeten doen) dus bronnen kunnen blijven rond langer dan nodig. Onthoud dat het aanroepen van Dispose () geen inzamelingscyclus veroorzaakt of op enige manier ervoor zorgt dat de GC het object verzamelt / bevrijdt; het biedt eenvoudig de middelen om deterministischer de gebruikte resources op te schonen en de GC te vertellen dat deze opruiming al is uitgevoerd.

Het hele punt van IDisposable en het dispose-patroon gaat niet om het onmiddellijk vrijmaken van geheugen. De enige keer dat een oproep om te verwijderen feitelijk zelfs een kans heeft om onmiddellijk geheugen vrij te maken, is wanneer het het weggooien van het == false-scenario verwerkt en het manipuleren van niet-beheerde bronnen. Voor beheerde code wordt het geheugen niet teruggewonnen totdat de GC een verzamelcyclus uitvoert, waar je echt geen controle over hebt (behalve GC.Collect (), wat ik al eerder heb genoemd is geen goed idee).

Uw scenario is niet echt geldig, omdat strings in .NET geen onveranderde bronnen gebruiken en IDisposable niet implementeren, er is geen manier om hen te dwingen om 'opgeschoond' te worden.


36
2018-02-11 20:21



Er mogen geen verdere aanroepen zijn naar de methoden van een object nadat Dispose is aangeroepen (hoewel een object verdere aanroepen tot Dispose moet tolereren). Daarom is het voorbeeld in de vraag dwaas. Als Dispose wordt aangeroepen, kan het object zelf worden weggegooid. Dus de gebruiker zou alle verwijzingen naar dat hele object moeten weggooien (zet ze op nul) en alle gerelateerde objecten erbinnen worden automatisch opgeruimd.

Wat betreft de algemene vraag over managed / unmanaged en de discussie in andere antwoorden, denk ik dat elk antwoord op deze vraag moet beginnen met een definitie van een onbeheerde resource.

Waar het op neer komt is dat er een functie is die je kunt gebruiken om het systeem in een staat te brengen, en er is een andere functie die je kunt gebruiken om die uit die staat terug te halen. Nu, in het typische voorbeeld, kan de eerste een functie zijn die een bestandsingang retourneert, en de tweede kan een aanroep van zijn CloseHandle.

Maar - en dit is de sleutel - het kunnen willekeurige bijpassende functies zijn. De een bouwt een staat op, de ander scheurt hem neer. Als de staat is gebouwd maar nog niet is afgebroken, bestaat er een instantie van de bron. U moet ervoor zorgen dat de demontage op het juiste moment gebeurt - de resource wordt niet beheerd door de CLR. Het enige automatisch beheerde hulpmiddeltype is geheugen. Er zijn twee soorten: de GC en de stapel. Waardetypes worden beheerd door de stapel (of door een rit te koppelen binnen de referentietypen) en referentietypen worden beheerd door de GC.

Deze functies kunnen statuswijzigingen veroorzaken die vrij kunnen worden doorgeschoten of moeten perfect worden genest. De statuswijzigingen kunnen threadveilig zijn of niet.

Kijk naar het voorbeeld in Justice's vraag. Wijzigingen in de inspringing van het logbestand moeten perfect worden genest of het loopt allemaal mis. Ook is het onwaarschijnlijk dat ze threadsafe zijn.

Het is mogelijk om een ​​lift te maken met de garbagecollector om ervoor te zorgen dat uw onbeheerde bronnen worden opgeruimd. Maar alleen als de functies voor statuswijziging thread-veilig zijn en twee staten levensduren kunnen hebben die elkaar overlappen. Dus het voorbeeld van Justice van een resource mag GEEN finalist hebben! Het zou gewoon niemand helpen.

Voor dat soort hulpmiddelen kunt u het gewoon implementeren IDisposable, zonder een finalizer. De finalizer is absoluut optioneel - het moet zijn. Dit is verdoezeld of zelfs niet genoemd in veel boeken.

Je moet dan de gebruiken using verklaring om daar enige kans toe te hebben Dispose wordt genoemd. Dit is in wezen hetzelfde als een ritje maken met de stapel (dus de finalizer is voor de GC, using is naar de stapel).

Het ontbrekende deel is dat je handmatig Dispose moet wegschrijven en het naar je velden en je basisklasse moet laten bellen. C ++ / CLI-programmeurs hoeven dat niet te doen. De compiler schrijft het in de meeste gevallen voor hen.

Er is een alternatief, wat ik verkies voor staten die perfect nestelen en niet threadveilig zijn (behalve wat dan ook, het vermijden van IDisposable spaart u het probleem van het hebben van een ruzie met iemand die het niet kan laten om een ​​finalizer toe te voegen aan elke klasse die IDisposable implementeert) .

In plaats van een klasse te schrijven, schrijf je een functie. De functie accepteert een afgevaardigde om terug te bellen naar:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

En dan zou een eenvoudig voorbeeld zijn:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

De lambda die wordt doorgegeven, fungeert als een codeblok, dus het is alsof je je eigen controlestructuur maakt om hetzelfde doel te dienen using, behalve dat u niet langer het gevaar loopt dat de beller het misbruikt. Op geen enkele manier kunnen ze falen om de bron schoon te maken.

Deze techniek is minder handig als de resource de soort is die overlappende levens kan hebben, omdat je dan resource A, dan resource B, kunt bouwen en vervolgens resource A wilt vernietigen en vervolgens resource B wilt vernietigen. Dat kun je niet doen als je de gebruiker hebt gedwongen om perfect te nestelen op deze manier. Maar dan moet je gebruiken IDisposable (maar nog steeds zonder een finalizer, tenzij je een beveiligingscode hebt geïmplementeerd die niet gratis is).


17
2018-02-11 19:31



Scenario's Ik maak gebruik van IDisposable: opschonen van onbeheerde bronnen, afmelden voor evenementen, nauwe connecties

Het idioom dat ik gebruik voor het implementeren van IDisposable (niet threadsafe):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

14
2018-02-11 18:20



Als MyCollection wordt hoe dan ook vuilnis opgehaald, dan zou je het niet moeten weggooien. Als u dat wel doet, hoeft de CPU alleen maar harder te worden gebruikt dan nodig is en kan zelfs een aantal vooraf berekende analyses ongeldig worden gemaakt die de garbage collector al heeft uitgevoerd.

ik gebruik IDisposable om dingen te doen zoals zorgen dat threads correct worden verwijderd, samen met onbeheerde bronnen.

BEWERK Als reactie op de opmerking van Scott:

De enige keer dat de GC-prestatiestatistieken worden beïnvloed, is wanneer een call de [sic] GC.Collect () is gemaakt "

Conceptueel onderhoudt de GC een weergave van de objectreferentiegrafiek en alle verwijzingen ernaar vanuit de stapelframes van threads. Deze heap kan behoorlijk groot zijn en vele pagina's geheugen beslaan. Als een optimalisatie slaat de GC de analyse op van pagina's die waarschijnlijk niet vaak worden gewijzigd om te voorkomen dat de pagina onnodig opnieuw wordt gescand. De GC ontvangt een melding van de kernel wanneer gegevens op een pagina worden gewijzigd, zodat deze weet dat de pagina vies is en een opnieuw scannen vereist. Als de verzameling zich in Gen0 bevindt, is het waarschijnlijk dat andere dingen op de pagina ook veranderen, maar dit is minder waarschijnlijk in Gen1 en Gen2. Anekdotisch waren deze hooks niet beschikbaar in Mac OS X voor het team dat de GC naar Mac had geport om de Silverlight-invoegtoepassing op dat platform te laten werken.

Een ander punt tegen onnodige verwijdering van middelen: stel je een situatie voor waarin een proces aan het lossen is. Stel je ook voor dat het proces al enige tijd loopt. De kans is groot dat veel van de geheugenpagina's van dat proces zijn omgewisseld naar schijf. Op zijn minst zitten ze niet meer in L1- of L2-cache. In een dergelijke situatie heeft het geen zin dat een toepassing die aan het lossen is al die gegevens en codetabellen terug in het geheugen ruilt om middelen vrij te maken die door het besturingssysteem worden vrijgegeven wanneer het proces wordt beëindigd. Dit is van toepassing op beheerde en zelfs bepaalde onbeheerde bronnen. Alleen bronnen die niet-achtergronddraden in leven houden moeten worden verwijderd, anders blijft het proces in leven.

Tijdens de normale uitvoering zijn er vluchtige bronnen die op de juiste manier moeten worden opgeruimd (zoals @fezmonkey opmerkt) databaseverbindingen, stopcontacten, raamgrepen) om onbeheerde geheugenlekken te voorkomen. Dit zijn de dingen die moeten worden verwijderd. Als u een klasse maakt die eigenaar is van een thread (en in eigendom bedoelt ik dat deze de thread heeft gemaakt en daarom verantwoordelijk is om ervoor te zorgen dat deze stopt, althans door mijn coderingsstijl), dan moet die klasse waarschijnlijk worden geïmplementeerd IDisposable en haal de draad af tijdens Dispose.

Het .NET-framework gebruikt de IDisposable interface als een signaal, zelfs een waarschuwing, voor ontwikkelaars die deze klasse moet weggedaan. Ik kan geen types bedenken in het framework dat wordt geïmplementeerd IDisposable (exclusief expliciete interface-implementaties) waarbij verwijdering optioneel is.


11
2018-02-11 18:19



Jazeker, die code is helemaal overbodig en onnodig en het maakt de vuilnisman niet iets dat hij anders niet zou doen (als een exemplaar van MyCollection eenmaal buiten bereik raakt). Vooral de .Clear() noemt.

Antwoord op uw bewerking: soort. Als ik dit doe:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Het is functioneel identiek aan dit voor geheugenbeheer:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Als je echt heel erg echt het geheugen van dit moment wilt vrijmaken, bel dan GC.Collect(). Er is echter geen reden om dit te doen. Het geheugen zal worden vrijgegeven wanneer het nodig is.


10
2018-06-03 21:07



Als je wilt verwijder nu meteen, gebruik onbeheerd geheugen.

Zien:


7
2018-02-11 21:08



Ik zal niet de gebruikelijke dingen herhalen over het gebruiken of bevrijden van niet-beheerde bronnen, die allemaal zijn behandeld. Maar ik zou willen wijzen op wat een veel voorkomende misvatting lijkt.
Gezien de volgende code

Public Class LargeStuff
  Implementeert IDisposable
  Privé _Large als string ()

  'Een vreemde code die' groot 'betekent, bevat enkele miljoenen lange reeksen.

  Public Sub Dispose () Implements IDisposable.Dispose
    _Large = niets
  End Sub

Ik besef dat de Wegwerp-implementatie niet voldoet aan de huidige richtlijnen, maar hopelijk krijgen jullie allemaal het idee.
Welnu, wanneer Dispose wordt genoemd, hoeveel geheugen wordt er dan vrijgemaakt?

Antwoord: Geen.
Calling Dispose kan onbeheerde bronnen vrijgeven, het KAN GEEN beheerd geheugen terugvorderen, alleen de GC kan dat doen. Dat wil niet zeggen dat het bovenstaande geen goed idee is, het volgen van het bovenstaande patroon is in feite nog steeds een goed idee. Nadat Dispose is uitgevoerd, is er niets meer wat de GC doet stoppen met het opnieuw claimen van het geheugen dat door _Large werd gebruikt, hoewel de instantie van LargeStuff mogelijk nog steeds in scope is. De reeksen in _Large kunnen ook in gen 0 zijn maar de instantie van LargeStuff kan gen 2 zijn, dus nogmaals, het geheugen zou eerder opnieuw worden geclaimd.
Het heeft geen zin om een ​​finaliser toe te voegen om de hierboven beschreven Dispose-methode aan te roepen. Dat vertraagt ​​het opnieuw claimen van geheugen om de finaliser te laten lopen.


6
2018-02-11 21:07