Megosztás a következőn keresztül:


Erőforrás-kezelési minta

Megjegyzés:

Ezt a tartalmat a Pearson Education, Inc. engedélyével nyomtatjuk újra a Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition című műből. Ezt a kiadást 2008-ban adták ki, és a könyvet azóta teljesen átdolgozták a harmadik kiadásban. Előfordulhat, hogy az oldalon található információk némelyike elavult.

Minden program egy vagy több rendszererőforrást, például memóriát, rendszerfogópontot vagy adatbázis-kapcsolatot szerez be a végrehajtás során. A fejlesztőknek óvatosnak kell lenniük az ilyen rendszererőforrások használatakor, mert a beolvasásuk és használatuk után ki kell őket szabadítani.

A CLR támogatja az automatikus memóriakezelést. A felügyelt memóriát (a C# operátorral newlefoglalt memóriát) nem kell explicit módon felszabadítani. A szemétgyűjtő (GC) automatikusan kibocsátja. Ez felszabadítja a fejlesztőket a memória felszabadításának fárasztó és nehéz feladatától, és ez volt az egyik fő oka a .NET-keretrendszer által biztosított példátlan termelékenységnek.

Sajnos a felügyelt memória csak egyike a rendszererőforrások számos típusának. A felügyelt memórián kívüli erőforrásokat továbbra is explicit módon kell felszabadítani, és nem felügyelt erőforrásoknak nevezzük. A GC-t kifejezetten nem az ilyen nem felügyelt erőforrások kezelésére tervezték, ami azt jelenti, hogy a nem felügyelt erőforrások kezelésének felelőssége a fejlesztők kezében van.

A CLR segítséget nyújt a nem felügyelt erőforrások felszabadításában. System.Object Deklarál egy virtuális metódust Finalize (más néven véglegesítőt), amelyet a GC hív meg, mielőtt az objektum memóriáját a GC visszanyerné, és felül lehet bírálni a nem felügyelt erőforrások felszabadításához. A véglegesítőt felülbíráló típusokat véglegesíthető típusoknak nevezzük.

Bár a véglegesítők bizonyos tisztítási forgatókönyvekben hatékonyak, két jelentős hátrányuk van:

  • A véglegesítő akkor van meghívva, amikor a szemétgyűjtő észleli, hogy egy objektum begyűjtésre jogosult. Ez egy meghatározatlan időpontban történik, miután az erőforrásra már nincs szükség. A fejlesztő által kívánt és a ténylegesen felszabadított erőforrás közötti késedelem elfogadhatatlan lehet olyan programokban, amelyek sok szűkös erőforrást vesznek igénybe (azaz könnyen kimeríthető erőforrásokat), vagy olyan esetekben, amikor az erőforrások fenntartása költséges (például nagy, nem felügyelt memóriapufferek esetében).

  • Amikor a CLR-nek véglegesítőt kell hívnia, el kell halasztania az objektum memóriájának gyűjtését a szemétgyűjtés következő fordulójáig (a véglegesítők a gyűjtemények között futnak). Ez azt jelenti, hogy az objektum memóriája (és az általa hivatkozott összes objektum) hosszabb ideig nem lesz felszabadítva.

Ezért előfordulhat, hogy a kizárólag véglegesítőkre hagyatkozás sok esetben nem megfelelő, ha fontos a nem felügyelt erőforrások lehető leggyorsabban történő visszaigénylése, a szűkös erőforrások kezelésekor, vagy olyan nagy teljesítményű forgatókönyvekben, amelyekben a véglegesítés hozzáadott GC-többlettere elfogadhatatlan.

A keretrendszer biztosítja a System.IDisposable implementálandó felületet, amely lehetővé teszi a fejlesztő számára a nem felügyelt erőforrások manuális kiadását, amint nincs rájuk szükség. Ez a módszer azt is lehetővé teszi GC.SuppressFinalize , hogy a GC megállapítsa, hogy egy objektumot manuálisan ártalmatlanítottak, és nem kell többé véglegesíteni, ebben az esetben az objektum memóriája korábban visszanyerhető. Az interfészt IDisposable megvalósító típusokat eldobható típusoknak nevezzük.

A Dispose minta a véglegesítők és a IDisposable felület használatának és implementálásának szabványosítására szolgál.

A minta fő motivációja az, hogy csökkentse a Finalize és a Dispose metódusok megvalósításának összetettségét. Az összetettség abból ered, hogy a metódusok néhány, de nem minden kódútvonalon osztoznak (a különbségeket a fejezet későbbi részében ismertetjük). Emellett a minta egyes elemeinek történelmi okai is vannak a determinisztikus erőforrás-kezelés nyelvi támogatásának fejlődésével kapcsolatban.

✓ A DO implementálja az egyszerű ártalmatlanítási mintát az eldobható típusok példányait tartalmazó típusok esetében. Az alapmintával kapcsolatos részletekért tekintse meg az Alapvető Dispose Minta szakaszt.

Ha egy típus felelős más eldobható objektumok élettartamáért, a fejlesztőknek is szükségük van egy módszerre azok eldobására. A tároló Dispose metódusának használata kényelmes módja annak, hogy ezt lehetővé tegye.

✓ A DO implementálja az alapszintű ártalmatlanítási mintát, és véglegesítőt biztosít az olyan erőforrásokat tartalmazó típusok számára, amelyeket explicit módon kell felszabadítani, és amelyek nem rendelkeznek véglegesítőkkel.

A mintát például nem felügyelt memóriapuffereket tároló típusok esetében kell implementálnia. A Véglegesíthető típusok szakasz a véglegesítők implementálásával kapcsolatos irányelveket ismerteti.

✓ Fontolja meg az alapszintű felszabadítási minta implementálását olyan osztályokon, amelyek maguk nem rendelkeznek nem felügyelt erőforrásokkal vagy eldobható objektumokkal, de valószínűleg rendelkeznek olyan altípusokkal, amelyek igen.

Erre jó példa az System.IO.Stream osztály. Bár ez egy absztrakt alaposztály, amely nem rendelkezik erőforrásokkal, a legtöbb alosztálya igen, és emiatt implementálja ezt a mintát.

Alapszintű felszabadítási minta

A minta alapvető implementációja az System.IDisposable interfész implementálását foglalja magában, valamint annak a metódusnak a deklarálását, amely az összes erőforrás-tisztítási logikát megvalósítja, és amit a Dispose(bool) metódus és az opcionális véglegesítő osztanak meg.

Az alábbi példa az alapminta egyszerű implementálását mutatja be:

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();
        }
    }
}

A logikai paraméter disposing azt jelzi, hogy a metódust a megvalósításból vagy a IDisposable.Dispose véglegesítőből hívták-e meg. Az Dispose(bool) implementációnak ellenőriznie kell a paramétert, mielőtt más referenciaobjektumokhoz (például az előző mintában szereplő erőforrásmezőhöz) fér hozzá. Ezeket az objektumokat csak akkor szabad elérni, ha a metódust a IDisposable.Dispose végrehajtásból hívják meg (ha a disposing paraméter értéke igaz). Ha a metódus meghívása a véglegesítőből történik (disposing hamis), más objektumok nem érhetők el. Ennek az az oka, hogy az objektumok kiszámíthatatlan sorrendben lesznek véglegesítve, így ezek vagy függőségeik már véglegesítve lehetnek.

Ez a szakasz azokra az osztályokra is vonatkozik, amelyek olyan alapokkal rendelkeznek, amelyek még nem implementálják a Lecsengési mintát. Ha olyan osztályból örököl, amely már implementálja a mintát, egyszerűen írja felül a Dispose(bool) metódust, hogy további erőforrás-tisztítási logika biztosítása érdekében.

✓ DO deklarál egy metódust protected virtual void Dispose(bool disposing) , amely központosítja a nem felügyelt erőforrások kiadásával kapcsolatos összes logikát.

Ebben a metódusban minden erőforrás-törlésnek meg kell történnie. A metódust meghívja mind a finalizer, mind a IDisposable.Dispose metódus. A paraméter hamis lesz, ha egy véglegesítőn belülről hívják meg. Ezzel biztosítható, hogy a véglegesítés során futó kódok ne férhessenek hozzá más véglegesíthető objektumokhoz. A véglegesítők implementálásának részleteit a következő szakaszban ismertetjük.

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

✓ DO implementálja a IDisposable felületet azzal, hogy egyszerűen hívja meg a Dispose(true), majd a GC.SuppressFinalize(this).

Csak akkor történjen meg a SuppressFinalize hívás, ha a Dispose(true) sikeresen végrehajtódik.

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

X NE tegye virtuálissá a paraméter nélküli metódust Dispose .

Az alosztályoknak fel kell bírálniuk a Dispose(bool) metódust.

// 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 NE deklarálja a metódus túlterhelését, kivéve Dispose és Dispose()Dispose(bool).

Dispose fenntartott szónak kell tekinteni, amely segít kodifikálni ezt a mintát, és megelőzni a megvalósítók, a felhasználók és a fordítók közötti félreértéseket. Egyes nyelvek dönthetnek úgy, hogy bizonyos típusokon automatikusan implementálják ezt a mintát.

✓ A DO lehetővé teszi a Dispose(bool) metódus többszöri meghívását. A metódus dönthet úgy, hogy nem tesz semmit az első hívás után.

public class DisposableResourceHolder : IDisposable {

    bool disposed = false;

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

X KERÜLJE a kivétel dobását belülről Dispose(bool), kivéve azokat a kritikus helyzeteket, amikor a futó folyamat sérült (szivárgások, inkonzisztens megosztott állapot stb.).

A felhasználók azt várják, hogy a hívás Dispose nem fog kivételt képezni.

Ha Dispose kivételt okozhatna, a további blokkok törlési logikája nem lesz végrehajtva. Ennek megkerüléséhez a felhasználónak be kell csomagolnia minden hívást Dispose (a végső blokkon belül!) egy próbablokkba, ami nagyon összetett törléskezelőkhöz vezet. Soha ne dobjon kivételt egy Dispose(bool disposing) metódus végrehajtásakor, ha az elhárítás hamis. Ez a művelet megszakítja a folyamatot, ha egy véglegesítő környezetben hajtja végre.

✓ DO olyan tagot dob, ObjectDisposedException amely az objektum megsemmisítése után nem használható.

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;
    }
}

✓ FONTOLJA MEG a(z) Close() metódus megadását, a Dispose() mellett, ha az "if close" a standard terminológia a területen.

Fontos, hogy a Close implementáció azonos legyen a Dispose-el, és fontolja meg a IDisposable.Dispose módszer explicit implementálását.

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

Véglegesíthető típusok

A véglegesíthető típusok olyan típusok, amelyek kibővítik az alapvető Dispose mintát a véglegesítő felülírásával és véglegesítési kód elérési út megadásával a Dispose(bool) metódusban.

A véglegesítők közismerten nehezen implementálhatók helyesen, elsősorban azért, mert a végrehajtás során nem lehet bizonyos (általában érvényes) feltételezéseket tenni a rendszer állapotáról. Az alábbi irányelveket körültekintően kell figyelembe venni.

Vegye figyelembe, hogy az irányelvek némelyike nem csak a Finalize metódusra vonatkozik, hanem bármilyen, véglegesítőből hívott kódra is. A korábban definiált alapszintű elidegenítési minta esetében ez azt a logikát jelenti, amely Dispose(bool disposing) akkor fut, ha a disposing paraméter hamis.

Ha az alaposztály már véglegesíthető, és implementálja az alapszintű elidegenítési mintát, ne bírálja felül Finalize újra. Ehelyett csak felül kell írnia a Dispose(bool) metódust, hogy további erőforrás-tisztítási logikát biztosítson.

Az alábbi kód egy véglegesíthető típus példáját mutatja be:

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 AVOID , hogy a típusok véglegesíthetők legyenek.

Alaposan fontolja meg azokat az eseteket, amikor úgy gondolja, hogy finalizálóra van szükség. A véglegesítőkkel rendelkező példányok valós költségekkel járnak, mind teljesítmény, mind kódbonyolítás szempontjából. Előnyben részesítse az erőforrásburkolókat, mint például a SafeHandle, a nem felügyelt erőforrások beburkolására, ahol lehetséges. Ebben az esetben a finalizer szükségtelenné válik, mert a burkoló felelős a saját erőforrás-tisztításáért.

X NE tegye kivezethetővé az értéktípusokat.

A CLR csak a referenciatípusokat véglegesíti, így a rendszer figyelmen kívül hagyja a véglegesítő értéktípusra tett minden kísérletet. A C# és a C++ fordítók kényszerítik ezt a szabályt.

✓ A DO véglegesíthetővé tesz egy típust, ha a típus felelős egy nem felügyelt erőforrás felszabadításáért, amely nem rendelkezik saját véglegesítővel.

A véglegesítő megvalósításakor egyszerűen hívja meg Dispose(false) és helyezze el az összes erőforrás-törlési logikát a Dispose(bool disposing) metóduson belül.

public class ComplexResourceHolder : IDisposable {

    ~ComplexResourceHolder() {
        Dispose(false);
    }

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

✓ DO implementálja az alapszintű ártalmatlanítási mintát minden véglegesíthető típuson.

Ez lehetővé teszi az ilyen típusú felhasználók számára, hogy explicit módon végezzenek determinisztikus tisztítást ugyanazon erőforrásokról, amelyekért a véglegesítő felelős.

X NE érje el a véglegesíthető objektumokat a véglegesítő kód elérési útján, mert jelentős a veszélye annak, hogy már véglegesítve lesznek.

Például egy véglegesíthető A objektum, amely egy másik véglegesíthető objektumra hivatkozik, B nem használható megbízhatóan az A véglegesítőjében, vagy fordítva. A végrehajtókat véletlenszerű sorrendben hívjuk meg (a kritikus véglegesítés gyenge rendezési garanciája nélkül).

Vegye figyelembe azt is, hogy a statikus változókban tárolt objektumok összegyűjtése bizonyos pontokon történik az alkalmazástartomány kiürítése vagy a folyamatból való kilépés során. Egy véglegesíthető objektumra hivatkozó statikus változó elérése (vagy statikus változókban tárolt értékeket használó statikus metódus meghívása) nem biztos, ha Environment.HasShutdownStarted igaz értéket ad vissza.

✓ TEGYE a Finalize metódust védetté.

A C#, a C++ és a VB.NET fejlesztőknek nem kell emiatt aggódniuk, mert a fordítók segítenek érvényesíteni ezt az útmutatót.

X NE hagyja, hogy a kivételek kikerüljenek a véglegesítő logikából, kivéve a rendszerkritikus hibákat.

Ha egy véglegesítő kivételt kap, a CLR leállítja a teljes folyamatot (a .NET-keretrendszer 2.0-s verziójától kezdve), megakadályozva, hogy más véglegesítők ellenőrzött módon hajtsanak végre és az erőforrásokat felszabadítsa.

✓ Fontolja meg egy kritikus fontosságú véglegesíthető objektum (egy típushierarchiával CriticalFinalizerObjectrendelkező típus) létrehozását és használatát olyan helyzetekben, amikor a véglegesítőnek feltétlenül végre kell hajtania még a kényszerített alkalmazástartományok kiürítése és a szálak megszakítása esetén is.

© Részletek 2005, 2009 Microsoft Corporation. Minden jog fenntartva.

Újranyomva a Pearson Education, Inc. engedélyével, Krzysztof Cwalina és Brad Abrams Framework Design Guidelines: Konvenciók, Idiomák és Minták az Újrafelhasználható .NET Könyvtárak Számára, 2. kiadás című könyvéből, közzétéve 2008. október 22-én, a Addison-Wesley Professional által, a Microsoft Windows Fejlesztési Sorozat részeként.

Lásd még