1. 程式人生 > >重學c#系列——非託管例項(五)

重學c#系列——非託管例項(五)

### 前言 託管資源到是好,有垃圾回收資源可以幫忙,即使需要我們的一些小小的除錯來優化,也是讓人感到欣慰的。但是非託管資源就顯得蒼白無力了,需要程式設計師自己去設計回收,同樣有設計的地方也就能體現出程式設計師的設計水平。 託管類在封裝對非託管資源的直接引用或者間接引用時,需要制定專門的規則,確保非託管資源在回收類的一個例項時釋放。 為什麼要確保呢? 是這樣子的,畫一個圖。 ![](https://img2020.cnblogs.com/blog/1289794/202007/1289794-20200725133500811-1999552491.png) 上圖中託管中生成並引用非託管,一但非託管和託管中的引用斷開(託管資源被回收),那麼這個時候非託管資源還在,那麼釋放這個問題就有一丟丟困難。 常見的有兩種機制來自動釋放非託管資源。 1. 宣告一個構析函式作為一個類的一個成員。 2. 在類中實現System.IDisposable. 好的,接下來就開始看例子吧。 ### 正文 #### 構析函式 先從構析函式看起吧。 ``` class Resource { ~Resource() { //釋放資源 } } ``` 在IL中是這樣子的。 ``` protected override void Finalize() { try { //構析函式寫的 } finally { base.Finalize(); } } ``` 簡單介紹一下這個Finalize 是一個終結器,我們無法重寫,文件中原文是這樣子的。 ``` 從包裝非託管資源的 SafeHandle 派生的類(推薦),或對 Object.Finalize 方法的重寫。 SafeHandle 類提供了終結器,因此你無需自行編寫。 ``` 這個SafeHandle 是啥呢?是安全控制代碼。這東西學問很大,非該文重點,先可以理解為控制代碼即可。 這裡簡單介紹一下控制代碼。 職業盜圖: ![](https://img2020.cnblogs.com/blog/1289794/202007/1289794-20200725171730240-812625125.png) 再次職業盜圖: ![](https://img2020.cnblogs.com/blog/1289794/202007/1289794-20200725171841107-146010118.png) 假設有一個控制代碼為0X00000AC6。有一個區域儲存這各個物件的地址,0X00000AC6指向這個區域裡面的區域A,A只是這個區中的一個。這個A指向真實的物件在記憶體中的位置。 這時候就有疑問了,那麼不是和指標一個樣子嗎?唯一不同的是指標的指標啊。是的,就是指標的指標。但是為啥要這麼做呢? 是這樣子的,物件在記憶體中的位置是變化的,而不是不變的。我們有時候看到電腦下面冒紅燈,這時候產生了虛擬記憶體,實際就是把硬碟當做記憶體了。但是我們發現電腦有點卡後,但是程式沒有崩潰。 當物件記憶體寫入我們的硬碟,使用的時候又讀出來了,這時候記憶體地址是變化了。這時候在記憶體中的操作是區域A的值變化了,而控制代碼的值沒有變化,因為它指向區域A。 現在我們通過實現構析函式來實現釋放非託管資源,那麼這種方式怎麼樣呢?這種方式是存在問題的,所以現在c#的構析函式去釋放非託管談的也不多。 主要問題如下: 1. 無法確認構析函式何時執行,垃圾回收機制不會馬上回收這個物件,那麼也就不會立即執行構析函式。 2. 構析函式的實現會延遲該物件在記憶體中的存在時間。沒有構析函式的物件,會在垃圾回收器中一次處理從記憶體中刪除,實現構析函式的物件需要兩次。 然後所有物件的終結器是由一個執行緒來完成的,如果Finalize中存在複雜的業務操作,那麼系統性能下降是可以預見的。 ### 實現IDisposable 看例子: ``` class Resource : IDisposable { public void Dispose() { //釋放資源 } } ``` 然後只要用完呼叫Dispose即可。 但是可能有時候程式設計師忘記主動呼叫了Dispose。 所以改成這樣。 ``` class Resource : IDisposable { bool _isDisposed=false; public void Dispose() { //釋放資源 _isDisposed = true; //標誌不用掉解構函式 GC.SuppressFinalize(this); } ~Resource() { if (_isDisposed) { return; } this.Dispose(); } } ``` 那麼是否這樣就結束了呢? 不是的。 文件中這樣介紹道:任何非密封類都應具有要實現的附加 Dispose(bool) 過載方法。 為什麼這樣說呢?因為是這樣子的,不是密封類,那麼可能會成為某個類的基類,那麼子類就要考慮基類如何釋放啊,所以加一個過載方法。 ``` 注:從終結器呼叫時,disposing 引數應為 false,從 IDisposable.Dispose 方法呼叫時應為 true。 換言之,確定情況下呼叫時為 true,而在不確定情況下呼叫時為 false。 ``` ``` class Resource : IDisposable { bool _isDisposed=false; public void Dispose() { //釋放資源 Dispose(true); //標誌不用掉解構函式 GC.SuppressFinalize(this); } ~Resource() { this.Dispose(false); } protected virtual void Dispose(bool disposing) { if (_isDisposed) { return; } if (disposing) { //釋放託管相關資源 } //釋放非託管資源 _isDisposed = true; } } ``` 看下思路: Dispose(bool) 方法過載 方法的主體包含兩個程式碼塊: ``` 釋放非託管資源的塊。 無論 disposing 引數的值如何,都會執行此塊。 釋放託管資源的條件塊。 如果 disposing 的值為 true,則執行此塊。 它釋放的託管資源可包括: 實現 IDisposable 的託管物件。 可用於呼叫其 Dispose 實現(級聯釋放)的條件塊。 如果你已使用 System.Runtime.InteropServices.SafeHandle 的派生類來包裝非託管資源,則應在此處呼叫 SafeHandle.Dispose() 實現。 佔用大量記憶體或使用短缺資源的託管物件。 將大型託管物件引用分配到 null,使它們更有可能無法訪問。 相比以非確定性方式回收它們,這樣做釋放的速度更快。 ``` 那麼為什麼明確去釋放實現IDisposable 的託管資源呢? 文件中回答是這樣子的: ``` 如果你的類擁有一個欄位或屬性,並且其型別實現 IDisposable,則包含類本身還應實現 IDisposable。 例項化 IDisposable 實現並將其儲存為例項成員的類,也負責清理。 這是為了幫助確保引用的可釋放型別可通過 Dispose 方法明確執行清理。 ``` 給個完整例子。 ``` csharp class Resource : IDisposable { bool _isDisposed=false; private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true); public void Dispose() { //釋放資源 Dispose(true); //標誌不用掉解構函式 GC.SuppressFinalize(this); } ~Resource() { this.Dispose(false); } protected virtual void Dispose(bool disposing) { if (_isDisposed) { return; } if (disposing) { _safeHandle?.Dispose(); //釋放託管相關資源 } //釋放非託管資源 _isDisposed = true; } } ``` _safeHandle 和 Resource 一樣同樣可以通過構析函式去釋放非託管,但是呢,如果自己Resource 主動Dispose去釋放,那麼最好把它的子物件(託管)的Dispose給執行了,好處上面寫了。 那麼這時候為什麼在構析函式中為顯示為false呢?因為構析函式這時候本質是在終結器中執行,屬於系統那一套,有太多不確定因素了,所以乾脆_safeHandle 自己去呼叫自己解構函式。 後來我發現.net core和.net framework,他們的構析函式執行方式是不一樣的。 舉個栗子: ``` static void Main(string[] args) { { Resource resource = new Resource(); } GC.Collect(); Console.Read(); } ``` 在.net framework 中馬上回去呼叫構析函式,但是在.net core中並不會,等了幾分鐘沒有反應。 原因可以在: https://github.com/dotnet/corefx/issues/5205 知道了大概怎麼回事。 好的,回到非託管中來。 那麼繼承它的子類怎麼寫呢? ``` class ResourceChild: Resource { bool _isDisposed = false; ~ResourceChild() { Dispose(false); } protected override void Dispose(bool disposing) { if (_isDisposed) { return; } if (disposing) { //釋放託管相關資源 } //釋放非託管資源 _isDisposed = true; base.Dispose(); } } ``` 非託管有太多的東西了,比如說非同步dispose,using。在此肯定整理不完,後續另外一節補齊。 ### 結 後一節,