5.C#釋放非託管資源1

釋放非託管資源

在介紹釋放非託管資源的時候,我以爲有必要先來認識一下啥叫非託管資源,既然有非託管資源,確定有託管資源.

 

 

託管資源指的是.net能夠自棕進行回收的資源,主要是指託管堆上分配的內存資源.託管資源的回收工做是不須要人工干預的,有.net運行庫在合適的調用垃圾回收器進行回收.

 

非託管資源指的是.net不知道如何回收的資源,最多見的一類非託管資源是包裝操做系統資源的對象,例如文件,窗口,網絡鏈接,數據庫鏈接,畫刷,圖標等.這類資源,垃圾回收器在清理的時候會調用Object.Finalize()方法.默認狀況下,方法是空的,對於非託管資源,須要在此方法中編寫回收非託管資源的代碼,以便垃圾回收器正確回收資源.

 

 

在.net中,Object.Finalize()方法是沒法重載的,編譯器是根據類的析構函數來自動生成Object.Finalize()方法的,因此對於包含非託管資源的類,能夠將釋放非託管資源的代碼放在析構函數中.

 

注意,不能再析構函數中釋放託管資源,由於析構函數是由垃圾回收器調用的,可能在析構函數調用以前,類包含的託管資源已經被回收了,從而致使沒法預知的結構.

 

 

原本呢,若是按照上面作法,非託管資源也可以由垃圾回收器進行回收,可是非託管資源通常是有限的,比較寶貴的,而垃圾回收器是由CLR(不知道CLR的看我上一篇文章)自動調用的,這樣就沒法保證一級的釋放非託管資源,所以定義了一個Dispose()方法,讓使用者可以手動的釋放非託管資源.Dispose()方法是防雷的託管資源和非託管資源,使用者手動調用此方法後,垃圾回收期不會對此類實例再次進行回收.Dispose()方法是有使用者調用的,在調用時,類的託管資源和非託管資源確定都尚未被回收,因此能夠同時回收這二者資源.

 

微軟爲非託管資源的回收專門定義了一個接口:IDisposable,接口中只能包含一個Dispose()方法.任何包含非託管資源的類,都應該繼承此接口.

 

 

在一個包含非託管資源的類中,關於資源釋放的標準作法是:

(1)  繼承IDisposeable接口;

(2) 事項Dispose()方法,在其中釋放託管資源和非託管資源,並將對象自己從垃圾回收器中移除(垃圾回收器不在回收此資源);

(3) 實現類析構函數,在其中釋放非託管資源

 

 

在使用的時候,顯示調用Dispose()方法,能夠及時的釋放資源,同時經過移除Finalize()方法的執行,提升了性能;若是沒有顯示調用Dispose()方法,垃圾回收器也能夠經過析構函數來釋放非託管資源,垃圾回收器自己就具備回收託管資源的功能,從而保證資源的正常釋放,只不過由垃圾回收器回收會致使非託管資源的未能及時釋放.

 

在.net中應該儘量的少使用析構函數釋放資源.在沒有析構函數的對象在垃圾處理器一次處理中從內存刪除,但有析構函數的對象,須要兩次,第一次調用析構函數,第二次刪除對象.並且在析構函數中包含大量的釋放資源代碼,會下降垃圾回收器的工做項鍊,影響性能.因此對於包含非託管資源的對象,最好及時的調用Dispose()方法來回收資源,而不是依賴垃圾回收器.

 

上面就是.net中對包含非託管資源的類的資源釋放幾隻,只要按照上述的步驟編寫代碼,累就屬於資源安全的類.

案例:

class MyResourceWrapper:IDisposable

{

    public void Dispose()

    {

        Console.WriteLine("release resources with Dispose");

        Console.Beep();

    }

}

 

class Program

{

    static void Main(string[] args)

    {

        MyResourceWrapper mr = new MyResourceWrapper();

        mr.Dispose();

    }

}

 

 

當咱們顯示調用Dispose方法的時候,能夠聽到系統的蜂鳴聲.

 

注意:通多Dispose進行資源的釋放也是有潛在的風險的,由於Dispose方法須要被程序員顯示的滴啊用,若是代碼中漏掉了Dispose的調用或者在Dispose調用以前產生了異常從而沒有指定Dispose,那麼有些資源可能就一直留在內存中了.

 

因此咱們應該使用下面的方式保證Dispose方法能夠被調用到:

static void Main(string[] args)

{

    MyResourceWrapper mr = new MyResourceWrapper();

    try

    {

        //do something wiht mr object

    }

    finally

    {

        mr.Dispose();

    }

}

 

 

可是,每次編寫Idspose的代碼都是用try塊會以爲很麻煩,還好C#中,咱們能夠重用using關鍵字來簡化Dispose的調用.

 

重用using關鍵字

在C#中,using語句提供了一個高效的調用對象Dispose方法的方式.對於任何IDisposable接口的類型,均可以使用using語句,而對於那些沒有實現IDisposable接口的類型,使用using語句會致使一個編譯錯誤.

 

static void Main(string[] args)

{

    using (MyResourceWrapper mr = new MyResourceWrapper())

    {

        //do something with mr object

    }

}

 

在using語句塊結束的時候,mr實例的Dispose方法會被自動調用,using語句不只免除了程序員輸入Dispose調用的代碼,他還保證Dispose方法被調用,不管using語句塊是否順利執行. 事實上,C#編譯器爲using語句自動添加了try/finally語句塊.

 

 

 

.NET提供了兩種釋放非託管資源的方式,一種是Finalize方法和Dispose方法.

 

接下來再來看Finalize方法

在.net的基類System.Object中,定義了名爲Finalize()的虛方法,這個方法默認什麼都不作.咱們能夠爲自定義的類型重寫Finalize方法,在該方法中加入必要的非託管資源清理邏輯.當要從內尊中刪除這個類型的對象時,垃圾回收器會調用對象的Finalize方法.因此,不管.net進行一次自發的垃圾回收,仍是經過GC.Collect()進行強制垃圾回收,Finalize方法老是會被調用.另外,當承載應用程序的AppDomain從內存中移除時,一樣會調用Finalize方法.

 

重寫Finalize方法

假設咱們如今有一個是由非託管資源的類型,咱們就須要重寫Finalize方法來進行非託管資源的清理,可是當經過下面的方式重寫Finalize方法的時候,會出現編譯錯誤:

    class MyResourceWrapper

    {

        protected override void Finalize()

        { }

}

 

其實,當咱們想要重寫Finalize方法時,C#爲咱們提供了(相似C++)析構函數語法(C#終結器)來重寫該方法.C#終結器和構造函數語法相似,方法名和類型名同樣;不一樣的是,終結器具備~前綴,而且不能使用訪問修飾符,不接受參數,不能重載,因此一個類只能有一個終結器.

 

    class MyResourceWrapper

    {

        ~MyResourceWrapper()

        {

            Console.WriteLine("release unmanaged resources");

            Console.Beep();//t經過控制檯播放器播放提示音

        }

    }

 

之因此C#只支持這種方式進行Finalize方法的重寫,是由於C#編譯器回味Finalize方法隱式的加入一些必要的基礎代碼.添加的那些基礎代碼我就不給大家看了,反正就是保證finalize方法老是能被執行.

 

 

當咱們執行下面的代碼的時候,咱們就能夠聽到蜂鳴聲了:

MyResourceWrapper mr = new MyResourceWrapper();

 

 

Finalize的工做機制

他的工做機制很複雜,想要深刻研究,您能夠本身上網查看(估計沒人查),我只是參考網上一些比較簡單的說法來理解Finalize的工做機制.

其實,Finalize方法的調是至關耗費資源的,Finalize方法的做用是保證.net對象可以在垃圾回收時清零非託管資源,若是建立了一個不使用非託管資源的類型,實現終結器是沒有任何做用的,因此,若是沒有特殊的須要應該避免重寫Finalize方法.

 

 

 

 

Dispose和Finalize的結合

 

Finalize能夠經過垃圾回收進行自動的調用,而Dispose須要被代碼顯示的調用.so,爲了保險起見,對於一些非託管資源,仍是有必要實現終結器的(什麼是終結器呢,看最後),也就是說,若是咱們忘記了顯式的調用Dispose,那麼垃圾回收也會調用Finalize,從而保證非託管資源的回收.

 

在MSND中提供了一種很好的模式來實現IDisposable接口來結合Dispose和FInalize,案例以下:

    class MyResourceWrapper:IDisposable

    {

        private bool IsDisposed = false;

        ~MyResourceWrapper()

        {

 

            Dispose(false);           

 

        }

        public void Dispose(bool disposing)

        {

            if (!this.IsDisposed)

            {

                if (disposing)

                {

                    //清除託管資源

                }

                //清除非託管資源

            }

            this.IsDisposed = true;

        }

        public void Dispose()

        {

            Dispose(true);

            //告訴GC不調用Finalize方法

            GC.SuppressFinalize(this);

        }

}

 

在這個模式中,void Dispose(bool disposing)函數經過一個disposing參數來區別當前是不是被Dispose()調用.若是是被Dispose()調用.若是是被Dispose()調用,那麼須要同時釋放託管和非託管資源.若是是被終結器調用了,那麼只須要釋放非託管的資源便可.Dispose()函數是被其餘代碼顯式調用應要求釋放資源的,而Finalize是被GC調用的.

 

另外,因爲在Dispose()中已經釋放了託管和非託管的資源,所以在對象中被GC回收時再次調用Finalize是沒有必要的,因此在Dispose()中調用GC.SuppressFinalize(this)避免重複調用Finalize.一樣,由於IsDisposed變量的存在,資源只會被釋放一次,多餘的調用會被忽略.

 

這個模式的優勢以下:

1.若是沒有顯式的調用Dispose(),未釋放託管和非託管資源,那麼在垃圾回收時,還會執行Finalize,釋放非託管資源,同時GC會釋放託管資源.

2.若是調用了Dispose(),就能及時釋放託管和非託管資源,那麼該對象被垃圾回收時,就不會執行Finalize(),提升了非託管資源的使用效率並提高了系統性能.


---------------------
做者:見證大牛成長之路
來源:CSDN
原文:https://blog.csdn.net/shanyongxu/article/details/47321007
版權聲明:本文爲博主原創文章,轉載請附上博文連接!程序員

相關文章
相關標籤/搜索