Dela via


Bortskaffa mönster

Kommentar

Det här innehållet skrivs om med behörighet från Pearson Education, Inc. från Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition. Den utgåvan publicerades 2008, och boken har sedan dess reviderats helt i den tredje utgåvan. En del av informationen på den här sidan kan vara inaktuell.

Alla program hämtar en eller flera systemresurser, till exempel minne, systemhandtag eller databasanslutningar, under körningen. Utvecklare måste vara försiktiga när de använder sådana systemresurser, eftersom de måste släppas efter att de har förvärvats och använts.

CLR har stöd för automatisk minneshantering. Hanterat minne (minne som allokeras med C#-operatorn new) behöver inte uttryckligen släppas. Den släpps automatiskt av skräpinsamlaren (GC). Detta befriar utvecklare från den omständliga och svåra uppgiften att frigöra minne och har varit en av de främsta orsakerna till den oöverträffade produktivitet som erbjuds av .NET Framework.

Tyvärr är hanterat minne bara en av många typer av systemresurser. Andra resurser än hanterat minne måste fortfarande släppas explicit och kallas ohanterade resurser. GC var särskilt inte utformat för att hantera sådana ohanterade resurser, vilket innebär att ansvaret för att hantera ohanterade resurser ligger i utvecklarnas händer.

CLR ger viss hjälp med att frigöra ohanterade resurser. System.Object deklarerar en virtuell metod Finalize (även kallad finalizer) som anropas av GC innan objektets minne frigörs av GC och kan åsidosättas för att frigöra ohanterade resurser. Typer som åsidosätter finalizern kallas slutgiltiga typer.

Även om slutförarna är effektiva i vissa rensningsscenarier har de två viktiga nackdelar:

  • Finalizern anropas när GC identifierar att ett objekt är berättigat till samling. Detta inträffar vid en obestämd tidsperiod efter att resursen inte längre behövs. Fördröjningen mellan när utvecklaren skulle kunna eller vill frigöra resursen och tiden när resursen faktiskt släpps av slutföraren kan vara oacceptabel i program som hämtar många knappa resurser (resurser som lätt kan förbrukas) eller i fall där resurser är kostsamma att behålla i bruk (t.ex. stora ohanterade minnesbuffertar).

  • När CLR behöver anropa en finalator måste den skjuta upp samlingen av objektets minne till nästa omgång skräpinsamling (finalizers körs mellan samlingar). Det innebär att objektets minne (och alla objekt som det refererar till) inte frigörs under en längre tid.

Därför är det kanske inte lämpligt att enbart förlita sig på slutförare i många scenarier när det är viktigt att frigöra ohanterade resurser så snabbt som möjligt, när du hanterar knappa resurser eller i mycket högpresterande scenarier där den extra GC-omkostnaderna för slutförande är oacceptabel.

Ramverket tillhandahåller det System.IDisposable gränssnitt som ska implementeras för att ge utvecklaren ett manuellt sätt att frigöra ohanterade resurser så snart de inte behövs. Den innehåller också den GC.SuppressFinalize metod som kan tala om för GC att ett objekt har tagits bort manuellt och inte behöver slutföras längre, i vilket fall objektets minne kan frigöras tidigare. Typer som implementerar IDisposable gränssnittet kallas disponibla typer.

Mönstret för bortskaffande är avsett att standardisera användningen och implementeringen av finalizers och IDisposable gränssnittet.

Den främsta motivationen för mönstret är att minska komplexiteten i implementeringen av Finalize metoderna och Dispose . Komplexiteten beror på att metoderna delar vissa men inte alla kodsökvägar (skillnaderna beskrivs senare i kapitlet). Dessutom finns det historiska orsaker till vissa delar av mönstret som rör utvecklingen av språkstöd för deterministisk resurshantering.

√ IMPLEMENTERA Basic Dispose Pattern på typer som innehåller instanser av disponibla typer. Mer information om det grundläggande mönstret finns i avsnittet Grundläggande bortskaffningsmönster.

Om en typ ansvarar för livslängden för andra engångsobjekt behöver utvecklare också ett sätt att göra sig av med dem. Att använda containerns Dispose metod är ett bekvämt sätt att göra detta möjligt.

√ Implementera Basic Dispose Pattern och tillhandahålla en slutversion av typer som innehåller resurser som måste frigöras explicit och som inte har finalizers.

Mönstret bör till exempel implementeras på typer som lagrar ohanterade minnesbuffertar. I avsnittet Finalizable Types beskrivs riktlinjer som rör implementering av finalizers.

√ ÖVERVÄG att implementera Basic Dispose Pattern på klasser som i sig inte innehåller ohanterade resurser eller disponibla objekt, men som sannolikt har undertyper som gör det.

Ett bra exempel på detta är System.IO.Stream klassen. Även om det är en abstrakt basklass som inte innehåller några resurser, så implementerar de flesta av dess underklasser det här mönstret.

Grundläggande sättningsmönster

Den grundläggande implementeringen av mönstret innebär att implementera System.IDisposable gränssnittet och deklarera den Dispose(bool) metod som implementerar all resursrensningslogik som ska delas mellan Dispose metoden och den valfria finaliseraren.

I följande exempel visas en enkel implementering av det grundläggande mönstret:

public class DisposableResourceHolder : IDisposable {

    private SafeHandle resource; // handle to a resource

    public DisposableResourceHolder() {
        this.resource = ... // allocates the resource
    }

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

    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            if (resource!= null) resource.Dispose();
        }
    }
}

Den booleska parametern disposing anger om metoden anropades från implementeringen IDisposable.Dispose eller från finalizern. Implementeringen Dispose(bool) bör kontrollera parametern innan du kommer åt andra referensobjekt (t.ex. resursfältet i föregående exempel). Sådana objekt bör endast nås när metoden anropas från implementeringen IDisposable.Dispose (när parametern disposing är lika med true). Om metoden anropas från finalizern (disposing är false) ska andra objekt inte nås. Anledningen är att objekt slutförs i en oförutsägbar ordning, så de, eller något av deras beroenden, kanske redan har slutförts.

Det här avsnittet gäller även klasser med en bas som inte redan implementerar mönstret Förfoga över. Om du ärver från en klass som redan implementerar mönstret åsidosätter Dispose(bool) du bara metoden för att tillhandahålla ytterligare resursrensningslogik.

√ Deklarera en protected virtual void Dispose(bool disposing) metod för att centralisera all logik som är relaterad till att frigöra ohanterade resurser.

All resursrensning bör ske i den här metoden. Metoden anropas från både finalizern och IDisposable.Dispose metoden. Parametern är falsk om den anropas inifrån en slutversion. Den bör användas för att säkerställa att all kod som körs under slutförande inte har åtkomst till andra slutgiltiga objekt. Information om hur du implementerar finalizers beskrivs i nästa avsnitt.

protected virtual void Dispose(bool disposing) {
    if (disposing) {
        if (resource!= null) resource.Dispose();
    }
}

√ ImplementeraIDisposable gränssnittet genom att helt enkelt anropa Dispose(true) följt av GC.SuppressFinalize(this).

Anropet till SuppressFinalize bör endast ske om Dispose(true) det körs korrekt.

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

X GÖR INTE den parameterlösa Dispose metoden virtuell.

Metoden Dispose(bool) är den som ska åsidosättas av underklasser.

// bad design
public class DisposableResourceHolder : IDisposable {
    public virtual void Dispose() { ... }
    protected virtual void Dispose(bool disposing) { ... }
}

// good design
public class DisposableResourceHolder : IDisposable {
    public void Dispose() { ... }
    protected virtual void Dispose(bool disposing) { ... }
}

X Deklarera INTE några överlagringar av metoden Dispose förutom Dispose() och Dispose(bool).

Dispose bör betraktas som ett reserverat ord för att hjälpa till att kodifiera detta mönster och förhindra förvirring bland implementerare, användare och kompilatorer. Vissa språk kan välja att automatiskt implementera det här mönstret på vissa typer.

√ TillåtDispose(bool) att metoden anropas mer än en gång. Metoden kan välja att inte göra något efter det första anropet.

public class DisposableResourceHolder : IDisposable {

    bool disposed = false;

    protected virtual void Dispose(bool disposing) {
        if (disposed) return;
        // cleanup
        ...
        disposed = true;
    }
}

X UNDVIK att utlösa ett undantag inifrån Dispose(bool) , förutom i kritiska situationer där innehållande process har skadats (läckor, inkonsekvent delat tillstånd osv.).

Användarna förväntar sig att ett anrop till Dispose inte skapar ett undantag.

Om Dispose det kan generera ett undantag körs inte ytterligare rensningslogik för slutligen blockering. För att undvika detta måste användaren omsluta varje anrop till Dispose (inom det sista blocket!) i ett försöksblock, vilket leder till mycket komplexa rensningshanterare. Om du kör en Dispose(bool disposing) metod kan du aldrig utlösa ett undantag om disponeringen är false. Om du gör det avslutas processen om den körs i en slutversionskontext.

√ Kasta en ObjectDisposedException från en medlem som inte kan användas efter att objektet har tagits bort.

public class DisposableResourceHolder : IDisposable {
    bool disposed = false;
    SafeHandle resource; // handle to a resource

    public void DoSomething() {
        if (disposed) throw new ObjectDisposedException(...);
        // now call some native methods using the resource
        ...
    }
    protected virtual void Dispose(bool disposing) {
        if (disposed) return;
        // cleanup
        ...
        disposed = true;
    }
}

√ ÖVERVÄG att Dispose()tillhandahålla metoden Close(), utöver , om stängning är standardterminologi i området.

När du gör det är det viktigt att du gör implementeringen Close identisk med och överväger att Dispose implementera IDisposable.Dispose metoden explicit.

public class Stream : IDisposable {
    IDisposable.Dispose() {
        Close();
    }
    public void Close() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Slutgiltiga typer

Finalizable-typer är typer som utökar mönstret för grundläggande bortskaffande genom att åsidosätta finalizern och tillhandahålla en slutkodsökväg i Dispose(bool) metoden.

Finalizers är notoriskt svåra att implementera korrekt, främst eftersom du inte kan göra vissa (normalt giltiga) antaganden om systemets tillstånd under körningen. Följande riktlinjer bör beaktas noggrant.

Observera att vissa riktlinjer inte bara gäller för Finalize metoden, utan för all kod som anropas från en finalizer. När det gäller det grundläggande mönstret för bortskaffning som tidigare definierats innebär det logik som körs inuti Dispose(bool disposing) när parametern disposing är false.

Om basklassen redan kan slutföras och implementerar Basic Dispose Pattern bör du inte åsidosätta Finalize den igen. Du bör i stället åsidosätta Dispose(bool) metoden för att tillhandahålla ytterligare resursrensningslogik.

Följande kod visar ett exempel på en slutlig typ:

public class ComplexResourceHolder : IDisposable {

    private IntPtr buffer; // unmanaged memory buffer
    private SafeHandle resource; // disposable handle to a resource

    public ComplexResourceHolder() {
        this.buffer = ... // allocates memory
        this.resource = ... // allocates the resource
    }

    protected virtual void Dispose(bool disposing) {
        ReleaseBuffer(buffer); // release unmanaged memory
        if (disposing) { // release other disposable objects
            if (resource!= null) resource.Dispose();
        }
    }

    ~ComplexResourceHolder() {
        Dispose(false);
    }

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

X UNDVIK att göra typerna slutgiltiga.

Tänk noga på alla fall där du tror att en finalator behövs. Det finns en verklig kostnad som är kopplad till instanser med finalizers, både ur prestanda- och kodkomplexitetssynpunkt. Använd gärna resursomslutningar som SafeHandle att kapsla in ohanterade resurser där det är möjligt, i vilket fall en slutförare blir onödig eftersom omslutningen ansvarar för rensningen av den egna resursen.

X GÖR INTE att värdetyper kan slutföras.

Endast referenstyper slutförs av CLR och därför ignoreras alla försök att placera en finalizer på en värdetyp. C#- och C++-kompilatorerna tillämpar den här regeln.

√ Gör en typ slutgiltig om typen är ansvarig för att frigöra en ohanterad resurs som inte har en egen finalizer.

När du implementerar slutföraren anropar Dispose(false) och placerar du bara all resursrensningslogik i Dispose(bool disposing) metoden.

public class ComplexResourceHolder : IDisposable {

    ~ComplexResourceHolder() {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing) {
        ...
    }
}

√ Implementera Basic Dispose Pattern på varje slutlig typ.

Detta ger användare av typen ett sätt att explicit utföra deterministisk rensning av samma resurser som slutföraren ansvarar för.

X FÅ INTE åtkomst till några slutgiltiga objekt i slutkodsökvägen, eftersom det finns en betydande risk för att de redan har slutförts.

Ett slutligt objekt A som har en referens till ett annat slutligt objekt B kan till exempel inte använda B på ett tillförlitligt sätt i A:s finalizer eller vice versa. Finalizers anropas i slumpmässig ordning (förutom en svag ordningsgaranti för kritisk slutförande).

Tänk också på att objekt som lagras i statiska variabler samlas in vid vissa tidpunkter när en programdomän tas bort eller när processen avslutas. Att komma åt en statisk variabel som refererar till ett slutligt objekt (eller anropa en statisk metod som kan använda värden som lagras i statiska variabler) kanske inte är säkert om Environment.HasShutdownStarted det returnerar sant.

√ Gör din Finalize metod skyddad.

C#, C++ och VB.NET utvecklare behöver inte bekymra sig om detta eftersom kompilatorerna hjälper till att framtvinga den här riktlinjen.

X Låt INTE undantag fly från slutpunktslogik, förutom systemkritiska fel.

Om ett undantag utlöses från en finalator stänger CLR av hela processen (från och med .NET Framework version 2.0), vilket förhindrar att andra finalizers körs och att resurser släpps på ett kontrollerat sätt.

√ ÖVERVÄG att skapa och använda ett kritiskt slutligt objekt (en typ med en typhierarki som innehåller CriticalFinalizerObject) för situationer där en slutförare absolut måste köras även inför tvingad programdomän som tar bort och tråden avbryts.

Portioner © 2005, 2009 Microsoft Corporation. Med ensamrätt.

Reprinted by permission of Pearson Education, Inc. from Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition by Krzysztof Cwalina and Brad Abrams, publicerad 22 okt 2008 av Addison-Wesley Professional som en del av Microsoft Windows Development Series.

Se även