熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

.net中非托管資源如何清理

2022-06-13   來源: .NET編程 

  背景
這兩天幫助其它項目組Review代碼發現有些地方實現了IDispose接口同時也發現了一些關於IDispose的問題:

         A類型實現了IDispose接口B類型裡面含有A類型的字段B類型沒有實現IDispose接口

         一個類裡面實現了Finalize終結器同時也實現了IDispose接口但在Dispose方法裡面沒有調用GCSuppressFinalize(this)方法

  下面我對以上兩個問題分別分析一下並提出解決方案

  問題:如果A類型裡面有非托管資源需要在實現的IDispose接口裡面釋放由於B類型沒有實現IDispose接口B類型的使用者要想釋放A類型的非托管資源並不方便這樣的話就有可能忘記了釋放A類型的非托管資源

  解決方案:
實現B類型的IDispose接口在Dispose方法裡面調用A類型的Dispose方法這樣B類型的使用者在調用B類型Dispose的同時就把A類型的Dispose也調用了

  問題:在Dispose方法裡面沒有調用GCSuppressFinalize(this)方法會有什麼問題呢這樣會導致垃圾回收器不能對這個類型的對象及時回收 當GC開始工作的時候它首先將沒有終結器的垃圾對象從內存中移除有終結器的所有對象則添加到一個垃圾隊列當中GC會調用一個新線程來執行這些對象的終結器當終結器執行完畢後這個對象會從隊列中被移除這個對象在隊列中移除之後當GC再次開始工作的時候這個對象才能夠被回收所以有終結器的對象會比沒有的在內存中保留更長的時間在後面我會對這裡再詳細的描述一下

  解決方案:

  在Dispose方法中調用GCSuppressFinalize(this)方法這樣的話就不會把有終結器的對象則添加到垃圾隊列當中

  切入正題<中,非托管代碼清理有兩種方式:Finalize方式和Dispose方式.

  Finalize方式:通過對自定義類型實現一個Finalize方法來釋放非通過資源.

  2.0開始,C#編譯器不能對Finalize進行顯示的調用和重寫,必須使用析構函數來實現它.

  class A

  {

  ~A()

  {

  釋放資源;

  }

  }

  上面的代碼就是通過Finalize方式來釋放資源的跟C++用析構函數釋放資源的代碼很象.

  但是它實現方式和C++不同,因為它是由垃圾回收器來管理內存的.

  大家看到了,用Finalize方式釋放非托管資源很簡單,但是如果你了解了他的實現方式,你可能就不會選擇用它來釋放非托管資源.

  那Finalize方式內部是如何實現的呢?

  當GC(垃圾回收器)開始工作的時候,它首先將沒有終結器的垃圾對象從內存中移除,有終結器的所有對象則添加到一個終止化隊列當中。tw.wiNgwiT.CoMGC會調用一個新線程來執行這些對象的終結器。當終結器執行完畢後,這些對象會從隊列中被移除。這時候由於這些對象在第一次檢測到的時候沒有被釋放,它們將會進入第1代對象,直到GC檢測到第0代對象和第1代對象再次充滿時,這時候GC才會把剛才那些對象釋放掉,所以有終結器的對象會比沒有的在內存中保留更長的時間。

  提示:垃圾回收器把托管堆中的對象分為3代,分別是0,1,2.一般分配為:0代約256K,1代約是2MB,第2代約是MB,代齡越高,容量就越大,顯然效率也就越低.首先被添加到托管堆中的對象被定為第0代,當第0代充滿時,就會執行垃圾回收,未被回收的對象代領將提升1代.

  由於以上原因應該避免僅使用Finalize方式釋放非托管資源.

  Dispose模式:在自定義類中實現IDispose接口,在接口中

  的Dispose方法中對非托管資源進行釋放.閒話少說,上代碼

  public class MyResourceRelease: IDisposable

  {

  ///

  /// 保證資源只用釋放一次

  ///

  private bool _alreadyDisposed = false;

  ///

  ///

  ///

  /// 用來判斷釋放資源的類別(托管和非托管)

  protected virtual void Dispose(bool isDisposing)

  {

  if(_alreadyDisposed)

  {

  return;

  }

  if(isDisposing)

  {

  //釋放托管資源

  }

  //釋放非托管資源

  _alreadyDisposed = true;

  }

  public void Dispose()

  {

  Dispose(true);

  }

  }

  上面的代碼就是用Dispose方式釋放資源的方法.因為上面自定義的Dispose(bool isDisposing)方法是virtual的,所以還可以在派生類裡面對它進行override

  public class MyDerivedResource: MyResourceRelease

  {

  private bool _disposed = false;

  protected override void Dispose(bool isDisposing)

  {

  if(_disposed)

  {

  return;

  }

  try

  {   

  if(isDisposing)

  {

  //釋放托管資源

  }

  //釋放非托管資源

  _disposed = true;

  }

  finally

  {

  base.Dispose(isDisposing);

  }

  }

  }

  這樣可以確保釋放繼承鏈上所有對象的引用資源,在整個繼承層次中傳播Dispose模式.

  思考

  那用Dispose方式非托管資源就是最好的方法了嗎?

  其實不然,因為類型實現了IDispose接口,這個類的使用者必須顯示調用Dispose方法,或者在創建該類型對象的時候使用using關鍵字,對於一些粗心的使用者可能會忘記調用Dispose方法,或者沒有使用using關鍵字,這樣就導致了非托管資源沒有釋放的後果.

  最佳方案

  同時實現終結器和Dispose方式.這樣對於細心的使用者直接顯示調用Dispose方法會提高垃圾回收的性能,對於粗心的使用者雖然忘記了調用Dispose方法,但也不至於使得非托管資源得不到釋放.

  注意這裡用到了GC. SuppressFinalize(this)方法.

  代碼如下:

  public class MyResourceRelease: IDisposable

  {

  ~MyResourceRelease()

  {

  Dispose(false);

  }

  ///

  /// 保證資源只用釋放一次

  ///

  private bool _alreadyDisposed = false;

  ///

  ///

  ///

  /// 用來判斷釋放資源的類別(托管和非托管)

  protected virtual void Dispose(bool isDisposing)

  {

  if(_alreadyDisposed)

  {

  return;

  }

  if(isDisposing)

  {

  //釋放托管資源

  }

  //釋放非托管資源

  _alreadyDisposed = true;

  }

  public void Dispose()

  {

  Dispose(true);

  //阻止GC把該對象放入終結器隊列

  GC.SuppressFinalize(this);

  }

  }


From:http://tw.wingwit.com/Article/program/net/201311/11606.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.