Çöp Toplayıcı IDisposable'ı arayacak mı?


134

.NET IDisposable Pattern , bir sonlandırıcı yazar ve IDisposable uygularsanız sonlandırıcının açıkça Dispose'i çağırması gerektiğini belirtir. Bu mantıklı ve sonlandırıcının garanti edildiği nadir durumlarda her zaman yaptığım şey bu.

Ancak, sadece bunu yaparsam ne olur:

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

ve bir sonlandırıcı veya başka bir şey uygulamayın. Çerçeve benim için Dispose yöntemini çağıracak mı?

Evet, bunun kulağa aptalca geldiğini anlıyorum ve tüm mantık olmayacağını ima ediyor, ama kafamın arkasında her zaman beni emin kılan 2 şey vardı.

  1. Birkaç yıl önce birileri bir keresinde bunun gerçekten yapacağını söyledi ve bu kişi "eşyalarını bilme" konusunda çok sağlam bir sicile sahipti.

  2. Derleyici / çerçeve, hangi arabirimleri uyguladığınıza bağlı olarak diğer 'sihirli' şeyler yapar (ör: foreach, genişletme yöntemleri, niteliklere dayalı serileştirme, vb.), Bu da bunun 'sihir' olabileceği mantıklıdır.

Bununla ilgili birçok şey okurken ve ima edilen birçok şey olsa da, bu soruya kesin bir Evet veya Hayır yanıtı bulamadım .

Yanıtlar:


121

Net Çöp Toplayıcı, çöp toplamadaki bir nesnenin Object.Finalize yöntemini çağırır. By varsayılan bunu yapar şey ve ek kaynak boşaltmak isterseniz overidden olmalıdır.

Bertaraf otomatik olarak adlandırılır DEĞİLDİR ve olmalıdır explicity böyle bir 'nihayet denemek' 'kullanarak' ya da blok içerisindeki olarak kaynaklarını serbest bırakılması halinde denir,

daha fazla bilgi için http://msdn.microsoft.com/tr-tr/library/system.object.finalize.aspx adresine bakın.


35
Aslında, GC'nin geçersiz kılınmamışsa Object.Finalize adını verdiğine inanmıyorum. Nesnenin etkili bir şekilde bir sonlandırıcıya sahip olmadığı belirlenir ve sonlandırma bastırılır - bu, nesnenin sonlandırma / freachable kuyruklarında olması gerekmediğinden daha verimli hale getirir.
Jon Skeet

7
MSDN'ye göre: msdn.microsoft.com/en-us/library/… C # 'da Object.Finalize yöntemini "geçersiz kılamaz", derleyici bir hata oluşturur: Nesneyi geçersiz kılma.Finalize. Bunun yerine bir yıkıcı sağlayın. ; yani, Sonlandırıcı olarak etkili bir şekilde hareket eden bir yıkıcı uygulamalısınız. [sadece kabul edilen cevap olduğu ve okunması muhtemel olduğu için tamlık için buraya eklendi]
Sudhanshu Mishra

1
GC, bir Sonlandırıcıyı geçersiz kılmayan bir nesneye hiçbir şey yapmaz. Sonlandırma kuyruğuna konulmaz - ve hiçbir Sonlandırıcı çağrılmaz.
Dave Black

1
@dotnetguy - orijinal C # belirtimi bir "yıkıcıdan" bahsetse bile, aslında bir Finalizer olarak adlandırılır - ve mekanik, gerçek bir "yıkıcı" nın yönetilmeyen diller için nasıl çalıştığından tamamen farklıdır.
Dave Black

67

Yorumunda Brian'ın amacını vurgulamak istiyorum, çünkü önemli.

Finalizörler C ++ gibi deterministik yıkıcılar değildir. Diğerleri işaret gibi, adı verilecek ne zaman garantisi yoktur ve siz, yeterli bellek varsa gerçekten o eğer hiç çağrılabilir.

Ancak finalizörlerin kötü yanı, Brian'ın dediği gibi, nesnenizin bir çöp koleksiyonunda hayatta kalmasına neden olmasıdır. Bu kötü olabilir. Neden?

Bildiğiniz veya bilmediğiniz gibi, GC nesillere bölünür - Gen 0, 1 ve 2 artı Büyük Nesne Yığını. Bölme gevşek bir terimdir - bir bellek bloğu alırsınız, ancak Gen 0 nesnelerinin başladığı ve bittiği işaretçiler vardır.

Düşünce süreci, kısa ömürlü olacak çok sayıda nesne kullanmanızdır. Yani bunlar GC'nin Gen 0 nesnelerine ulaşması için kolay ve hızlı olmalıdır. Bellek baskısı olduğunda, yaptığı ilk şey Gen 0 koleksiyonudur.

Şimdi, bu yeterli basıncı çözmezse, o zaman geri döner ve bir Gen 1 taraması yapar (Gen 0'ı yineleme) ve sonra hala yeterli değilse, Gen 2 taraması yapar (Gen 1 ve Gen 0'ı yineleme). Bu nedenle uzun ömürlü nesneleri temizlemek biraz zaman alabilir ve oldukça pahalı olabilir (çünkü iplikleriniz işlem sırasında askıya alınabilir).

Bu, eğer böyle bir şey yaparsanız:

~MyClass() { }

Nesneniz, ne olursa olsun, 2. Nesil'de yaşayacak. Bunun nedeni, GC'nin çöp toplama sırasında sonlandırıcıyı çağırmanın bir yolu olmamasıdır. Böylece sonlandırılması gereken nesneler, farklı bir iş parçacığı (sonlandırıcı iş parçacığı - eğer öldürürseniz her türlü kötü şeyi gerçekleştirir) tarafından temizlenmesi için özel bir sıraya taşınır. Bu, nesnelerinizin daha uzun süre asılı kalması ve potansiyel olarak daha fazla çöp koleksiyonunu zorlaması anlamına gelir.

Yani, tüm bunlar sadece mümkün olduğunda kaynakları temizlemek için IDisposable'ı kullanmak istediğiniz noktayı eve götürmek ve sonlandırıcıyı kullanmanın yollarını ciddi bir şekilde bulmaya çalışmaktır. Uygulamanızın yararına olacaktır.


8
Mümkün olduğunca IDisposable'ı kullanmak istediğinizi kabul ediyorum, ancak bir imha yöntemini çağıran bir sonlandırıcıya da sahip olmalısınız. IDispose.Dispose yöntemini çağırdıktan sonra nesnenizin sonlandırıcı kuyruğuna yerleştirilmediğinden emin olmak için GC.SuppressFinalize () yöntemini çağırabilirsiniz.
jColeson

2
Nesiller 1-3 değil, 0-2 olarak numaralandırılır, ancak yayınınız iyi. Bununla birlikte, nesneniz tarafından başvurulan herhangi bir nesnenin veya bu nesneler tarafından başvurulan herhangi bir nesnenin vb. De başka bir nesil için çöp toplama işlemine (sonlandırmaya karşı olmasa da) korunacağını da eklerim. Bu nedenle, sonlandırıcılara sahip nesneler, sonlandırma için gerekli olmayan hiçbir şeye referans vermemelidir.
Supercat


3
"Nesneniz, ne olursa olsun, 2. Nesil'de yaşayacak." Bu ÇOK temel bilgiler! Sonlandırma için "hazır" olan çok sayıda kısa ömürlü Gen2 nesnesinin olduğu bir sistemde hata ayıklamak için çok fazla zaman tasarrufu sağladı, ancak hiçbir zaman sonlandırılmadı, ağır yığın kullanımı nedeniyle OutOfMemoryException'a neden olmadı. Sonlandırıcının (hatta boş) çıkarılması ve kodun başka bir yere taşınması (çalışma), sorun ortadan kalktı ve GC yükü kaldırabildi.
kalemtıraş

@CoryFoy "Nesneniz, ne olursa olsun, 2. Nesile yaşayacak" Bunun için herhangi bir belge var mı?
Ashish Negi

33

Burada zaten çok iyi tartışmalar var ve partiye biraz geç kaldım, ama kendime birkaç puan eklemek istedim.

  • Çöp Toplayıcı sizin için hiçbir zaman doğrudan bir Dispose yöntemi yürütmez.
  • GC , sonlandırıcıları hissettiğinde yürütür.
  • Bir sonlandırıcıya sahip nesneler için kullanılan yaygın bir desen, açık bir Dispose çağrısı yerine sonlandırma nedeniyle çağrının yapıldığını belirtmek için yanlış yerleştirilen Dispose (bool çöken) olarak tanımlanan bir yöntem çağırmasını sağlamaktır.
  • Bunun nedeni, bir nesneyi sonlandırırken diğer yönetilen nesneler hakkında varsayımlarda bulunmanın güvenli olmamasıdır (zaten tamamlanmış olabilirler).

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

 public void Dispose() {
  Dispose(true);
 }

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

Bu basit sürüm, ancak bu modelde sizi harekete geçirebilecek birçok nüans var.

  • IDisposable.Dispose için yapılan sözleşme, birden çok kez çağrılmasının güvenli olması gerektiğini belirtir (zaten atılan bir nesneye Dispose çağrılması hiçbir şey yapmamalıdır)
  • Tek kullanımlık nesnelerin kalıtım hiyerarşisini, özellikle farklı katmanlar yeni Tek Kullanımlık ve yönetilmeyen kaynaklar getiriyorsa, düzgün bir şekilde yönetmek çok karmaşık olabilir. Yukarıdaki desende Dispose (bool), geçersiz kılınmasına izin vermek için sanaldır, ancak hataya açık olduğunu düşünüyorum.

Bence, hem tek kullanımlık referansları hem de kesinleştirme gerektirebilecek yerel kaynakları içeren herhangi bir türden tamamen kaçınmak çok daha iyidir. SafeHandles, yerel olarak kendi sonlandırmalarını sağlayan yerel kaynakları tek kullanımlık haline getirerek bunu yapmak için çok temiz bir yol sağlar (P / Invoke sırasında asenkron bir istisna nedeniyle yerel bir tutamacın kaybedilebileceği bir dizi başka avantajla birlikte) .

Sadece bir SafeHandle tanımlamak önemsiz kılar:


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

İçeren türü aşağıdakileri basitleştirmenizi sağlar:


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}

1
SafeHandleZeroOrMinusOneIsInvalid sınıfı nereden geliyor? Dahili bir .net tipi mi?
Orion Edwards

+1 for // Bence, hem tek kullanımlık referansları hem de kesinleştirme gerektirebilecek yerel kaynakları içeren herhangi bir türden tamamen kaçınmak çok daha iyidir .// Sonlandırıcıları olması gereken tek mühürsüz sınıflar, amacı Sonlandırma.
supercat


1
GC.SuppressFinalizeBu örnekte yapılan çağrı ile ilgili olarak . Bu bağlamda, SuppressFinalize yalnızca Dispose(true)başarılı bir şekilde yürütüldüğünde çağrılmalıdır . Eğer Dispose(true)sonuçlandırılması bastırılır sonra bir noktada başarısız ancak tüm kaynaklar (özellikle yönetilmeyen olanlar) temizlendiğinde önce, o zaman yine sonuçlandırma mümkün olduğunca temizleme olarak yapmak için gerçekleşmesini istiyoruz. GC.SuppressFinalizeÇağrıyı Dispose()yaptıktan sonra çağrıyı yönteme taşımak daha iyidir Dispose(true). Bkz Çerçeve Tasarım Kuralları ve bu yayını .
BitMask777

6

Ben öyle düşünmüyorum. Dispose çağrıldığında kontrolünüz vardır, bu da teorik olarak diğer nesnelerin varlığı (örneğin) hakkında varsayımlar yapan imha kodu yazabileceğiniz anlamına gelir. Sonlandırıcının çağrılması üzerinde hiçbir kontrole sahip değilsiniz, bu yüzden sonlandırıcının sizin adınıza otomatik olarak Atma'yı çağırması iffy olurdu.


EDIT: Sadece emin olmak için gittim ve test ettim:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}

Bertaraf sırasında kullanabileceğiniz nesneler hakkında varsayımlarda bulunmak, özellikle sonlandırma sırasında tehlikeli ve hileli olabilir.
Scott Dorman

3

Açıkladığınız durumda değil, Ama eğer varsa , GC sizin için Sonlandırıcıyı arayacaktır .

ANCAK. Bir sonraki çöp toplama, toplanmak yerine, sonlandırma que'sine girecek, her şey toplanacak, daha sonra sonlandırıcı olarak adlandırılacak. Bundan sonraki koleksiyon serbest bırakılacak.

Uygulamanızın bellek basıncına bağlı olarak, bir süre bu nesne oluşturma için bir gc'niz olmayabilir. Yani, bir dosya akışı veya bir db bağlantısı söz konusu olduğunda, yönetilmeyen kaynağın sonlandırıcı çağrısında bir süre serbest kalması için bir süre beklemeniz gerekebilir ve bu da bazı sorunlara neden olur.


1

Hayır, çağrılmadı.

Ancak bu, eşyalarınızı atmayı unutmayı kolaylaştırır. Sadece usinganahtar kelimeyi kullanın .

Bunun için aşağıdaki testi yaptım:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }

1
Bu, <code> kullanarak </code> anahtar kelimesini kullanmadığınızda, çağrılmayacağının bir örneğiydi ... ve bu kod parçasının 9 yıl kutlu olsun!
penyaskito

1

GC imha çağrısı yapmaz . Bu olabilir senin finalizer diyoruz, ama bu bile her koşulda garanti edilmez.

Bunu ele almanın en iyi yolu hakkında bir tartışma için bu makaleye bakın .


0

IDisposable ile ilgili belgeler , davranışın yanı sıra örnek kodun oldukça açık ve ayrıntılı bir açıklamasını verir. GC Dispose(), arabirimdeki yöntemi çağırmaz , ancak nesneniz için sonlandırıcıyı çağırır.


0

IDisposable deseni öncelikle geliştirici tarafından çağrılacak şekilde oluşturulmuştur, IDispose uygulayan bir nesneniz varsa, geliştiricinin ya usinganahtar kelimenin nesnenin bağlamı etrafında uygulanması ya da doğrudan Dispose yöntemini çağırması gerekir.

Model için başarısız olan kasa, Dispose () yöntemini çağıran sonlandırıcıyı uygulamaktır. Bunu yapmazsanız, bazı bellek sızıntıları oluşturabilirsiniz, örneğin: Bazı COM sarıcıları oluşturursanız ve hiçbir zaman System.Runtime.Interop.Marshall.ReleaseComObject (comObject) öğesini (Dispose yöntemine yerleştirilir) çağırmazsanız.

Clr'de, sonlandırıcıları içeren nesneleri izlemek ve bunları GC tarafından Finalizer tablosunda saklamak ve bazı temiz sezgisel tarama GC tarafından başlatıldığında çağırmak dışında otomatik olarak Dispose yöntemlerini çağırmak için sihir yoktur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.