2009-11-21 23 views
14

Bu konuyla ilgili birçok tartışma oldu ve herkes bir bellek sızıntısını önlemek için her zaman Delegate.EndInvoke'yi aramanız gerektiğine katılıyor (Jon Skeet söyledi!).Çağırma Delegate.EndInvoke bellek sızıntısına neden olabilir ... efsane?

Her zaman bu yönergeyi sorgulamadan takip ettim, ancak yakın zamanda kendi AsyncResult sınıfımı uyguladı ve sızıntıya neden olabilecek tek kaynağın AsyncWaitHandle olduğunu gördüm.

(Aslında WaitHandle tarafından kullanılan yerel kaynak, bir Finalizer'a sahip bir SafeHandle içinde kapsüllenmiş olduğu için gerçekten sızıntı yapmaz, yine de çöp toplayıcısının son haline getirme kuyruğuna baskı ekleyecektir.

Action a = delegate { }; 
while (true) 
    a.BeginInvoke(null, null); 

bir süre ve bellek için bu koştum: AsyncResult uygulanması sadece bir sızıntı sadece denemek orada olduğunu bilmek

) ... en iyi yolu talep üzerine AsyncWaitHandle başlatır 9-20 MB arasında kal.

en Delegate.EndInvoke çağrıldığında ile karşılaştıralım: garip Bu testte, 9-30 MG arasında hafıza oyun ile

Action a = delegate { }; 
while (true) 
    a.BeginInvoke(ar => a.EndInvoke(ar), null); 

, ha? (Muhtemelen bir AsyncCallback olduğunda çalıştırılması biraz daha uzun sürdüğü için, ThreadPool'da daha sıraya alınmış delege olacaktır)

Ne düşünüyorsunuz ... "Mit bozuldu"?

P.S. ThreadPool.QueueUserWorkItem Delegate.BeginInvoke bir yüz daha verimli, daha iyi & çağrıları unutmak için kullanmak için.

+2

İyi soru! +1. BTW, çok fazla izlersiniz MythBusters;) – RCIX

+0

Katılıyorum, ThreadPool.QueueUserWorkItem, parlak ve az kullanılan bir yöntemdir. – Anton

cevap

6

Bir Eylem temsilcisi çağırmak ve içinde bir istisna atmak için biraz deneme yaptım.Daha sonra, bir seferde sadece belirli bir sayıda çalışan iş parçacığını sürdürerek iş parçacığı havuzuna taşmadığından emin olun ve bir silme çağrısı sona erdiğinde iş parçacığı havuzu sürekli olarak doldurun.

static void Main(string[] args) 
{ 

    const int width = 2; 
    int currentWidth = 0; 
    int totalCalls = 0; 
    Action acc =() => 
    { 
     try 
     { 
      Interlocked.Increment(ref totalCalls); 
      Interlocked.Increment(ref currentWidth); 
      throw new InvalidCastException("test Exception"); 
     } 
     finally 
     { 
      Interlocked.Decrement(ref currentWidth); 
     } 
    }; 

    while (true) 
    { 
     if (currentWidth < width) 
     { 
      for(int i=0;i<width;i++) 
       acc.BeginInvoke(null, null); 
     } 

     if (totalCalls % 1000 == 0) 
      Console.WriteLine("called {0:N}", totalCalls); 

    } 
} 

yaklaşık 20 dakika ve Beginınvoke sonra aramaları özel bayt bellek tüketimi (23 MB) sabit oldu 30 milyon yanı sıra Kol sayımı yönetmesine izin sonra: İşte kodudur. Var olmak için bir sızıntı yok gibi görünüyor. Bir bellek sızıntısı olduğunu belirten CLR aracılığıyla Jeffry Richters C# kitabını okudum. En azından bu, artık .NET 3.5 SP1 ile doğru olmayacak gibi görünüyor.

Test Ortamı: Windows 7 x86 .NET 3.5 SP1 Intel 6600 Çift Çekirdekli 2,4 GHz

Sevgiler, şu anda bellek sen bağlı olmalıdır şey değildir sızıntısı olmadığı Alois Kraus

2

Bazı durumlarda, BeginInvoke'un EndInvoke'a (özellikle WinForms penceresiyle mesajlaşmaya) ihtiyacı yoktur. Ancak, bunun önemli olduğu durumlar vardır - asenkron iletişim için BeginRead ve EndRead gibi. Eğer bir ateş-ve-bırakmak BeginWrite yapmak istiyorsanız, muhtemelen bir süre sonra ciddi bellek sorun olur.

Yani, bir testiniz kesin olamaz. Sorunuzu doğru bir şekilde ele almak için pek çok farklı asenkron olay delegesi ile uğraşmanız gerekir.

+0

Muhtemelen bazı asenkron işlemlerin EndInvoke için bir çağrı gerektirdiği konusunda haklısınız, özellikle Delegate.BeginInvoke hakkında konuşuyordum. –

1

Makinemi birkaç dakika boyunca çalıştıran ve onu öldürmeye karar vermeden önce 3.5 GB çalışan kümesine ulaşan aşağıdaki örneği ele alalım.

Action a = delegate { throw new InvalidOperationException(); }; 
while (true) 
    a.BeginInvoke(null, null); 

NOT: ekli bir hata ayıklayıcı olmadan veya "istisna atılmış üzerinde mola" ile çalıştırın ve engelli "kullanıcı işlenmeyen özel durum kırmaya" emin olun.

DÜZENLEME: Jeff işaret gibi, hafıza sorunu burada bir sızıntı, ancak sadece daha hızlı o işlenebilir daha işi kuyruk tarafından sistemde yığılma bir durum değildir. Gerçekten de, aynı davranışı, atmanın uygun şekilde uzun bir işlemle değiştirilmesiyle gözlemlenebilir. Ve BeginInvoke çağrıları arasında yeterli zaman bırakırsak bellek kullanımı sınırlanır. Teknik olarak, orijinal soru cevapsız bırakılır. Ancak, bir sızıntıya neden olup olmadığına bakılmaksızın, Delegate.EndInvoke çağrılmasın, istisnaların göz ardı edilmesine neden olabileceğinden kötü bir fikirdir.

+1

İlginç, ama bunu denedin mi? Eylem a = delege {atma yeni InvalidOperationException(); }; süre (doğru) a.BeginInvoke (ar => { { a.EndInvoke (ar) deneyin; } mandalı {} } NULL); EndInvoke çağrılırken bellek tüketimi aynı görünüyor, bellek yalnızca bir istisna atarken onları çalıştırabileceğiniz sıraya göre daha çok iş öğesi kuyruğundan dolayı yüksek oluyor. –

+0

Haklısınız. Cevabımı düzelttim. –

11

. Çerçeve ekibi, gelecekte, bir sızıntıya neden olabilecek bir şekilde değişiklik yapabilir ve resmi politika “EndInvoke'u aramanız gerekir”, çünkü “tasarımla” olur.

Belgelenen gereksinimler üzerinde gözlenen davranışa güvenmeyi seçtiğiniz için, uygulamanızın gelecekte bir anda aniden sızıntı yapmaya başlaması olasılığını kullanmak ister misiniz?

+2

+1. Dahası, çerçevenin gelecekteki bir revizyonunda tehlikeler hakkında spekülasyon yapmaya gerek yoktur. Cevabımda belirttiğim gibi, bellek sızıntısı olasılığını bir kenara bırakarak, 'Delegate.EndInvoke' çağırmak, istisnaların göz ardı edilmesine neden olacak ve eylemin başarıyla tamamlanıp tamamlanmadığını bilmenin bir yolunu vermeyecektir. –

İlgili konular