1. 程式人生 > WINDOWS開發 >C# 託管資源與非託管資源(參考八)

C# 託管資源與非託管資源(參考八)

本文轉自:https://www.cnblogs.com/lyh523329053/articles/8051560.html

雖然在.NET程式設計過程中,絕大多數記憶體垃圾回收由CLR(公共語言執行時)自動回收,但也有很多需要我們編碼回收。掌握託管與非託管的基本知識,可以有效避免某些情況下導致的程式異常。

1.託管與非託管

1.1什麼是託管與非託管?

託管程式碼就是Visual Basic .NET和C#編譯器編譯出來的程式碼。編譯器把程式碼編譯成中間語言(IL),而不是能直接在你的電腦上執行的機器碼。中間語言被封裝在一個叫程式集(assembly)的檔案中,程式集中包含了描述你所建立的類,方法和屬性(例如安全需求)的所有元資料。你可以拷貝這個程式集到另一臺伺服器上部署它。通常來說,這個拷貝的動作就是部署流程中唯一的一個操作。

託管程式碼在公共語言執行庫(CLR)中執行。這個執行庫給你的執行程式碼提供各種各樣的服務,通常來說,他會載入和驗證程式集,以此來保證中間語言的正確性。當某些方法被呼叫的時候,執行庫把具體的方法編譯成適合本地計算機執行的機械碼,然後會把編譯好的機械碼快取起來,以備下次呼叫(這就是即時編譯)。隨著程式集的執行,執行庫會持續地提供各種服務,例如安全,記憶體管理,執行緒管理等等。這個程式被“託管”在執行庫中。Visual Basic .NET和C#只能產生託管程式碼。如果你用這類語言寫程式,那麼所產生的程式碼就是託管程式碼。

託管資源:一般是指被CLR(公共語言執行時)控制的記憶體資源,這些資源由CLR來管理。可以認為是.net類庫中的資源。

非託管資源:不受CLR控制和管理的資源。

對於託管資源,GC負責垃圾回收。對於非託管資源,GC可以跟蹤非託管資源的生存期,但是不知道如何釋放它,這時候就要人工進行釋放。

1.2哪些資源是非託管的?

非託管程式碼就是在Visual Studio .NET 2002釋出之前所建立的程式碼,例如Visual Basic 6,Visual C++ 6。 最糟糕的是,連那些依然殘存在你的硬碟中、擁有超過15年曆史的陳舊C編譯器所產生的程式碼都是非託管程式碼。非託管程式碼直接編譯成目標計算機的機械碼,這些程式碼只能執行在編譯出它們的計算機上,或者是其它相同處理器或者幾乎一樣處理器的計算機上。非託管程式碼不能享受一些執行庫所提供的服務,例如安全和記憶體管理等。如果非託管程式碼需要進行記憶體管理等服務,就必須顯式地呼叫作業系統的介面,通常來說,它們會呼叫Windows SDK所提供的API來實現。就最近的情況來看,非託管程式會通過COM介面來獲取作業系統服務。跟Visual Studio平臺的其他程式語言不一樣,Visual C++可以建立非託管程式。當你建立一個專案,並且選擇名字以MFC,ATL或者Win32開頭的專案型別,那麼這個專案所產生的就是非託管程式。

總而言之,非託管程式碼是執行在公共語言執行庫環境(CLR)的外部,由作業系統直接執行的程式碼。非託管程式碼必須提供自己的垃圾回收、型別檢查、安全支援等服務;它與託管程式碼不同,後者從公共語言執行庫中獲得這些服務。

總體來說就是不受CLR控制和管理的資源

包括:比如檔案流、影象圖形類、資料庫的連線,網路連線,系統的視窗控制代碼,印表機資源等,這類資源一般不存在堆上。可以認為作業系統資源的一組API。

原則:如果我們的類使用的非託管資源,如資料庫連線、檔案控制代碼,這些資源需做到:使用後立刻釋放。

1.3託管程式碼的執行過程

  1. 選擇編譯器:為獲得公共語言執行庫提供的優點,必須使用一個或多個針對執行庫的語言編譯器,如 Visual Basic、C#、Visual C++、JScript 或許多第三方編譯器(如 Eiffel、Perl 或 COBOL 編譯器)中的某一個。由於執行庫是一個多語言執行環境,因此它支援各種資料型別和語言功能。您所用的語言編譯器首先確定可用的執行庫功能,然後使用這些功能設計程式碼。編譯器(而不是執行庫)建立程式碼必須使用的語法。如果您的元件必須完全能夠被用其他語言編寫的元件使用,您的元件的匯出型別必須只公開公共語言規範(CLS) 中包括的語言功能。
  2. 編譯,將原始碼翻譯為microsoft中間語言(MSIL)並生成所需的元資料。
  3. 在執行時,實時 (JIT) 編譯器將 MSIL 翻譯為本機程式碼。在此編譯過程中,程式碼必須通過驗證過程,該過程檢查 MSIL 和元資料以檢視是否可以將程式碼確定為型別安全。
  4. 執行程式碼:公共語言執行庫提供使執行能夠發生以及可在執行期間使用的各種服務的結構。

2.如何釋放非託管資源?

.NET對於釋放資源,標準做法如下:

(1)繼承IDisposable介面;

(2)實現Dispose()方法,在其中釋放託管資源和非託管資源,並將物件本身從垃圾回收器中移除(垃圾回收器不在回收此資源);

(3)實現類解構函式,在其中釋放非託管資源。

對於Dispose()方法的幾個引數說明:

A.引數為true表示釋放所有資源,只能由使用者呼叫

 引數為false表示釋放非託管資源,只能由垃圾回收器自動呼叫

C.如果子類有自己的非託管資源,可以過載這個函式,新增自己的非託管資源的釋放

D.但是要記住,過載此函式必須保證呼叫基類的版本,以保證基類的資源正常釋放

2.1Dispose的實現方法

...有時間再寫..

2.2舉例說明資源釋放

2.2.1 資料庫連線釋放

(1)錯誤做法:

SqlConnection conn = new SqlConnection();
//do something;
conn.Dispose();

此段程式碼,如果出現異常conn.Dispose()未能及時執行,會導致沒有及時關閉連線。

(2)正確做法:

技術分享圖片
SqlConnection conn = new SqlConnection();
try
{
    //do something;
}
finally
{
    conn.Dispose();
}
技術分享圖片

對於以上程式碼,我們還可以簡化程式碼量,c#使用using簡化輸入,編譯器自動翻譯成try...finally,改為下面寫法

using(SqlConnection conn = new SqlConnection())
{
      //do something;
}

2.3對於釋放資源注意的幾點

A.顯示呼叫Dispose()方法,可以及時的釋放資源,同時通過移除Finalize()方法的執行,提高了效能;

B.如果沒有顯式呼叫Dispose()方法,垃圾回收器也可以通過解構函式來釋放非託管資源,垃圾回收器本身就具有回收託管資源的功能,從而保證資源的正常釋放,只不過由垃圾回收器回收會導致非託管資源的未及時釋放的浪費。

C.在.NET中應該儘可能的少用解構函式釋放資源。在沒有解構函式的物件在垃圾處理器一次處理中從記憶體刪除,但有解構函式的物件,需要兩次,第一次呼叫解構函式,第二次刪除物件。而且在解構函式中包含大量的釋放資原始碼,會降低垃圾回收器的工作效率,影響效能。

D.對於包含非託管資源的物件,最好及時的呼叫Dispose()方法來回收資源,而不是依賴垃圾回收器

E.解構函式只能由垃圾回收器呼叫。

F.Despose()方法只能由類的使用者呼叫。

G.在.NET中,凡是繼承了IDisposable介面的類,都可以使用using語句,從而在超出作用域後,讓系統自動呼叫Dispose()方法。
H.一個資源安全的類,都實現了IDisposable介面和解構函式。提供手動釋放資源和系統自動釋放資源的雙保險。

2.4 關於Finalize和Dispose

(1)、Finalize只釋放非託管資源;

(2)、Dispose釋放託管和非託管資源;

(3)、重複呼叫Finalize和Dispose是沒有問題的;

(4)、Finalize和Dispose共享相同的資源釋放策略,因此他們之間也是沒有衝突的。

==========================【Origin and Reference】==========================

https://www.cnblogs.com/yubinfeng/p/4625833.html

https://www.cnblogs.com/yangecnu/archive/2013/04/30/3052652.html

https://www.2cto.com/kf/201007/52838.html