Sdílet prostřednictvím


Uvolňovací vzor

Poznámka:

Tento obsah je znovu vytištěn oprávněním Pearson Education, Inc. z Framework Design Guidelines: Conventions, Idioms a Patterns for Reusable .NET Libraries, 2. vydání. Tato edice byla publikována v roce 2008 a kniha byla od té doby plně upravena ve třetím vydání. Některé informace na této stránce můžou být zastaralé.

Všechny programy získávají během provádění jeden nebo více systémových prostředků, jako je paměť, systémová zpracování nebo připojení k databázi. Vývojáři musí být opatrní při používání takových systémových prostředků, protože je nutné je po získání a použití uvolnit.

CLR poskytuje podporu pro automatickou správu paměti. Spravovaná paměť (přidělená paměť pomocí operátoru newjazyka C#) nemusí být explicitně vydána. Automaticky se o to postará garbage collector (GC). Tím se vývojářům uvolní zdlouhavá a obtížná úloha uvolnění paměti a je jedním z hlavních důvodů pro nevídanou produktivitu poskytovanou rozhraním .NET Framework.

Spravovaná paměť je bohužel jedním z mnoha typů systémových prostředků. Prostředky jiné než spravovaná paměť je potřeba explicitně uvolnit a nazývají se nespravované prostředky. GC nebyl speciálně navržen tak, aby spravoval takové nespravované prostředky, což znamená, že odpovědnost za správu nespravovaných prostředků leží v rukou vývojářů.

CLR poskytuje určitou pomoc při uvolňování nespravovaných prostředků. System.Object deklaruje virtuální metodu Finalize (označovanou také jako finalizátor), která je volána garbage collectorem před tím, než paměť objektu bude obnovena, a lze ji přepsat, aby uvolnila nespravované prostředky. Typy, které přepisují finalizátor, se označují jako finalizovatelné typy.

I když finalizátory jsou v některých scénářích čištění efektivní, mají dvě významné nevýhody:

  • Finalizátor je volán, když garbage collector zjišťuje, že objekt je vhodný ke sběru. K tomu dochází v určitém nedeterminovaném časovém období po tom, co už prostředek není potřeba. Zpoždění mezi tím, kdy by vývojář mohl nebo chtěl uvolnit prostředek, a čas, kdy je prostředek skutečně uvolněn finalizátorem, může být nepřijatelné v programech, které získávají mnoho málo prostředků (prostředky, které se dají snadno vyčerpat) nebo v případech, kdy jsou prostředky nákladné pro zachování používání (např. velké nespravované vyrovnávací paměti).

  • Když CLR potřebuje volat finalizátor, musí odložit sběr paměti objektu do dalšího kola sběru paměti (finalizátory se spouštějí mezi sběrnými cykly). To znamená, že paměť objektu (a všechny objekty, na které odkazuje) nebude uvolněna po delší dobu.

Proto spoléhat se výhradně na finalizátory nemusí být vhodné v mnoha scénářích, kdy je důležité co nejrychleji uvolnit neřízené prostředky, při práci s omezenými prostředky nebo v scénářích s vysokými požadavky na výkon, ve kterých je dodatečná režie uvolňování paměti při finalizaci nepřijatelná.

Rámec poskytuje System.IDisposable rozhraní, které by mělo být implementováno, aby poskytnul vývojářům ruční způsob, jak uvolnit nespravované prostředky, jakmile nejsou potřeba. Poskytuje také metodu GC.SuppressFinalize , která může informovat GC, že objekt byl ručně odstraněn a už není nutné dokončit, v takovém případě lze paměť objektu uvolnit dříve. Typy, které implementují IDisposable rozhraní, se označují jako jednorázové typy.

Model Dispose je určen ke standardizaci použití a implementace finalizátorů a IDisposable rozhraní.

Hlavní motivací modelu je snížit složitost implementace Finalize metod a Dispose metod. Složitost vychází ze skutečnosti, že metody sdílejí některé, ale ne všechny cesty kódu (rozdíly jsou popsány dále v kapitole). Kromě toho existují historické důvody pro některé prvky vzoru související s vývojem podpory jazyka pro deterministické řízení prostředků.

• Implementujte základní model Dispose u typů obsahujících instance uvolnitelných typů. Viz část Základní vzor Dispose pro podrobnosti o základním vzoru.

Pokud je typ zodpovědný za životnost jiných uvolnitelných objektů, vývojáři potřebují také způsob, jak je odstranit. Použití metody kontejneru Dispose je pohodlný způsob, jak to udělat.

• Implementujte základní model Dispose a poskytněte finalizátor typů obsahujících prostředky, které musí být explicitně uvolněny a které nemají finalizátory.

Model by měl být například implementován u typů, které ukládají nespravované vyrovnávací paměti. Část Finalizovatelné typy popisuje pokyny týkající se implementace finalizátorů.

• ZVAŽTE implementaci základního vzoru Dispose u tříd, které samy neobsahují nespravované prostředky ani objekty, které lze uvolnit, ale je pravděpodobné, že mají podtypy obsahující takové objekty.

Skvělým příkladem této třídy je System.IO.Stream . I když je to abstraktní základní třída, která nedrží žádné prostředky, většina jejích podtříd je drží, a z tohoto důvodu implementuje tento vzor.

Základní vzor Dispose

Základní implementace modelu zahrnuje implementaci System.IDisposable rozhraní a deklarování Dispose(bool) metody, která implementuje veškerou logiku čištění prostředků, která se má sdílet mezi metodou Dispose a volitelným finalizátorem.

Následující příklad ukazuje jednoduchou implementaci základního vzoru:

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

Logický parametr disposing označuje, zda byla metoda vyvolána z IDisposable.Dispose implementace nebo finalizátoru. Implementace Dispose(bool) by měla před přístupem k jiným referenčním objektům zkontrolovat parametr (např. pole zdroje v předchozí ukázce). Tyto objekty by měly být přístupné pouze v případě, že metoda je volána z IDisposable.Dispose implementace (pokud disposing je parametr roven true). Pokud je metoda vyvolána z finalizátoru (disposing je false), jiné objekty by neměly být přístupné. Důvodem je, že objekty jsou finalizovány v nepředvídatelném pořadí, takže již mohly být finalizovány samy nebo některé z jejich závislostí.

Tato část se vztahuje také na třídy se základem, který ještě neimplementuje Dispose Pattern. Pokud dědíte z třídy, která už model implementuje, jednoduše přepište metodu Dispose(bool) , aby poskytovala další logiku vyčištění prostředků.

• Deklarujte metodu, která centralizuje veškerou protected virtual void Dispose(bool disposing) logiku související s uvolněním nespravovaných prostředků.

K vyčištění všech prostředků by mělo dojít v této metodě. Metoda je volána jak z finalizátoru, tak z metody IDisposable.Dispose. Parametr bude false, pokud je vyvolán z uvnitř finalizátoru. Měl by se použít k zajištění, že veškerý kód spuštěný během finalizace nemá přístup k jiným finalizovatelným objektům. Podrobnosti o implementaci finalizátorů jsou popsány v další části.

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

✓ ImplementujteIDisposable rozhraní jednoduše voláním Dispose(true) následovaným GC.SuppressFinalize(this).

Volání SuppressFinalize by mělo proběhnout pouze v případě, že Dispose(true) se úspěšně provede.

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

X NEstavte metodu bez Dispose parametrů jako virtuální.

Metoda Dispose(bool) je ta, kterou by měly podtřídy přepsat.

// 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 NEDOPORUČUJEME deklarovat žádná přetížení metody kromě Dispose a Dispose().

Dispose by mělo být považováno za vyhrazené slovo, které pomůže tento vzor codififikovat a zabránit nejasnostem mezi implementátory, uživateli a kompilátory. Některé jazyky se můžou rozhodnout tento model automaticky implementovat u určitých typů.

• UmožňujeDispose(bool), aby byla metoda volána více než jednou. Metoda se může rozhodnout, že po prvním volání nic neudělá.

public class DisposableResourceHolder : IDisposable {

    bool disposed = false;

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

X AVOID vyvolá výjimku z rozsahu Dispose(bool) s výjimkou kritických situací, kdy byl proces obsahující poškozen (úniky, nekonzistentní sdílený stav atd.).

Uživatelé očekávají, že volání Dispose nevyvolá výjimku.

Pokud Dispose by mohla vyvolat výjimku, logika dalšího vyčištění bloku se nespustí. Aby to uživatel mohl obejít, bude muset zabalit každé volání na Dispose v rámci bloku finally do bloku try, což vede k velmi složitým obslužným rutinám čištění. Pokud provádíte metodu Dispose(bool disposing), nikdy nevyvolejte výjimku, pokud disponování není pravda. Tímto způsobem proces ukončíte, pokud se provádí uvnitř kontextu finalizátoru.

• Vyvolejte výjimku z kteréhokoli ObjectDisposedException člena, který nelze použít po zlikvidování objektu.

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

• ZVAŽTE poskytnutí metody Close(), kromě Dispose(), pokud je uzavření standardní terminologií v oblasti.

Při tom je důležité, abyste implementaci Close udělali stejnou jako Dispose a explicitně zvažte implementaci metody IDisposable.Dispose.

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

Finalizovatelné typy

Finalizovatelné typy jsou typy, které rozšiřují Základní Dispose Vzor přepsáním finalizátoru a poskytnutím cesty kódu finalizace v Dispose(bool) metodě.

Finalizátory se obtížně implementují správně, a to především proto, že během provádění nelze provést určité (obvykle platné) předpoklady o stavu systému. Pečlivě je třeba vzít v úvahu následující pokyny.

Všimněte si, že některé pokyny se vztahují nejen na metodu Finalize , ale na jakýkoli kód volaný z finalizátoru. V případě dříve definovaného vzoru Basic Dispose to znamená logiku, která se spustí uvnitř Dispose(bool disposing) , když disposing je parametr false.

Pokud je základní třída již finalizovatelná a implementuje Základní Dispose pattern, určitě byste neměli Finalize znovu přepisovat. Místo toho byste měli metodu Dispose(bool) přepsat, abyste poskytli další logiku pro vyčištění prostředků.

Následující kód ukazuje příklad finalizovatelného typu:

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 VYHNĚTE se tomu, aby typy byly finalizovatelné.

Pečlivě zvažte jakýkoli případ, ve kterém si myslíte, že finalizátor je potřeba. Z hlediska výkonu i složitosti kódu existují skutečné náklady spojené s instancemi s finalizátory. Pokud je to možné, používejte obálky prostředků, jako je SafeHandle, k zapouzdření nespravovaných prostředků; v takovém případě se finalizátor stává zbytečným, protože obálka zodpovídá za vyčištění vlastních prostředků.

X Nedělejte typy hodnot finalizovatelnými.

Modul CLR ve skutečnosti finalizuje pouze odkazové typy, a proto všechny pokusy o umístění finalizátoru na typ hodnoty budou ignorovány. Kompilátory C# a C++ vynucují toto pravidlo.

✓ Je vhodné stanovit typ jako finalizovatelný, pokud je typ zodpovědný za uvolnění neřízeného prostředku a nemá svůj vlastní finalizátor.

Při implementaci finalizátoru jednoduše zavolejte Dispose(false) a veškerou logiku vyčištění prostředků umístěte do metody Dispose(bool disposing).

public class ComplexResourceHolder : IDisposable {

    ~ComplexResourceHolder() {
        Dispose(false);
    }

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

• Implementujte základní odlikvidovací vzor na každém finalizovatelném typu.

To uživatelům tohoto typu umožňuje explicitně a deterministicky vyčistit ty samé prostředky, za které je finalizátor zodpovědný.

X NEMÁ přístup k žádným finalizovatelným objektům v cestě kódu finalizátoru, protože existuje značné riziko, že již budou dokončeny.

Například finalizovatelný objekt A, který má odkaz na jiný finalizovatelný objekt B nemůže spolehlivě použít B v finalizátoru A nebo naopak. Finalizační metody jsou volány v náhodném pořadí (s výjimkou slabé záruky řazení pro kritickou finalizaci).

Mějte také na paměti, že objekty uložené ve statických proměnných se během uvolnění domény aplikace nebo při ukončení procesu shromažďují v určitých bodech. Přístup ke statické proměnné, která odkazuje na finalizovatelný objekt (nebo volání statické metody, která může používat hodnoty uložené ve statických proměnných), nemusí být bezpečné, pokud Environment.HasShutdownStarted vrátí hodnotu true.

- Udělejte svou Finalize metodu chráněnou.

Vývojáři C#, C++ a VB.NET se o to nemusí starat, protože kompilátory pomáhají vynucovat toto vodítko.

X NENECHÁ VÝJIMKY utéct z logiky finalizátoru, s výjimkou systémově kritických selhání.

Pokud dojde k výjimce v rámci finalizátoru, CLR ukončí celý proces (od verze 2.0 .NET Framework), zabrání dalším finalizátorům ve vykonání a uvolnit prostředky řízeným způsobem.

✓ ZVAŽTE vytvoření a použití kritického finalizovatelného objektu (typ s hierarchií typů, která obsahuje CriticalFinalizerObject) pro situace, kdy finalizátor musí být bezpodmínečně proveden i v případě vynuceného uvolnění aplikační domény a přerušení vláken.

Části z © 2005, 2009 Microsoft Corporation. Všechna práva vyhrazena.

Přetištěno se svolením Pearson Education, Inc. z Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition od Krzysztofa Cwaliny a Brada Abramse, vydáno 22. října 2008 nakladatelstvím Addison-Wesley Professional jako součást série Microsoft Windows Development.

Viz také