.NET'te bir MemoryStream kapatılmadıysa bellek sızıntısı oluşur mu?


113

Takip koduna sahibim:

MemoryStream foo(){
    MemoryStream ms = new MemoryStream();
    // write stuff to ms
    return ms;
}

void bar(){
    MemoryStream ms2 = foo();
    // do stuff with ms2
    return;
}

Ayırdığım MemoryStream'in bir şekilde daha sonra atılmaması ihtimali var mı?

Bunu manuel olarak kapatmam konusunda ısrar eden bir meslektaş incelemem var ve geçerli bir noktası olup olmadığını söyleyecek bilgiyi bulamıyorum.


41
Yorumcunuza tam olarak neden onu kapatmanız gerektiğini düşündüğünü sorun. Genel iyi uygulamadan bahsediyorsa, muhtemelen akıllı davranmaktadır. Daha önce hafızayı serbest bırakmaktan bahsederse, yanılıyor.
Jon Skeet

Yanıtlar:


60

Tek kullanımlık bir şey varsa, onu her zaman atmalısınız. Disposed aldığınızdan emin olmak usingiçin bar()yönteminizde bir ifade kullanmalısınız ms2.

Sonunda çöp toplayıcı tarafından temizlenecektir, ancak Dispose'u çağırmak her zaman iyi bir uygulamadır. FxCop'u kodunuzda çalıştırırsanız, onu bir uyarı olarak işaretler.


16
Kullanan blok çağrıları sizin için yok edilir.
Nick

20
@Grauenwolf: İddianız kapsüllemeyi bozuyor. Bir tüketici olarak, işlemsiz olup olmadığına aldırmamalısınız: IDisposable ise, Dispose () sizin işinizdir.
Marc Gravell

4
Bu StreamWriter sınıfı için geçerli değildir: Bağlı akışı yalnızca StreamWriter'ı atarsanız atar - çöp toplanırsa ve sonlandırıcısı çağrılırsa akışı asla atmaz - bu tasarım gereğidir.
springy76

4
Bu sorunun 2008 yılına ait olduğunu biliyorum, ancak bugün .NET 4.0 Görev kitaplığımız var. Dispose () 'dir gereksiz içinde en vakaların Görevler kullanırken. IDisposable'ın " İşin bittiğinde bunu atsan iyi olur" anlamına gelmesi gerektiğine hemfikir olsam da , artık gerçekten bu anlamına gelmiyor.
Phil

7
IDisposable nesneyi atmamanız gereken başka bir örnek de HttpClient aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong IDisposable nesnenin olduğu ve atmanız gerekmeyen (veya hatta gerekmeyen) BCL'den bir başka örnek o. Bu,
BCL'de

167

En azından mevcut uygulamada hiçbir şey sızdırmayacaksınız.

Dispose çağırmak, MemoryStream tarafından kullanılan belleği daha hızlı temizlemeyecektir. Bu olacak ya sizin için yararlı olabilir veya olmayabilir çağrı, sonra okuma / yazma aramalar için geçerli olmaktan akışınızı durdurun.

Kesinlikle emin bunu iseniz asla akışının başka türlü bir MemoryStream gelen taşımak istiyor, size Dispose çağırmaz için herhangi bir zarar vermeyecek. Ancak, şimdiye kadar eğer kısmen genellikle iyi bir uygulamadır yapmak farklı bir Stream kullanmak için değişiklik, size erken kolay yolu seçti çünkü zor find böcek tarafından ısırıldı istemiyoruz. (Öte yandan, YAGNI argümanı var ...)

Zaten bunu yapmak için başka bir nedenle yeni bir uygulama olmasıdır olabilir Dispose üzerinde muaf tutuluyordu kaynaklarını tanıtmak.


Bu durumda, işlev bir MemoryStream döndürür çünkü "çağrı parametrelerine bağlı olarak farklı şekilde yorumlanabilen veriler" sağlar, bu nedenle bir bayt dizisi olabilirdi, ancak başka nedenlerden dolayı bir MemoryStream olarak yapılması daha kolaydı. Bu yüzden kesinlikle başka bir Stream sınıfı olmayacak.
Coderer

Bu durumda, hala ediyorum denemek inşa iyi alışkanlıklar vb - - sadece genel ilkesine bunu imha etmek için ama zor oldu çok fazla olursa endişe olmaz.
Jon Skeet

1
Biri kaynakları en kısa sürede serbest bırakmak konusunda gerçekten endişeleniyorsa, "kullanma" bloğunuzdan hemen sonra referansı boş bırakın, böylece yönetilmeyen kaynaklar (varsa) temizlenir ve nesne çöp toplama için uygun hale gelir. Yöntem hemen geri dönüyorsa, muhtemelen pek bir fark yaratmayacaktır, ancak yöntemde daha fazla bellek istemek gibi başka şeyler yapmaya devam ederseniz, kesinlikle bir fark yaratabilir.
Triynko

@Triynko Pek doğru değil: Ayrıntılar için bkz . Stackoverflow.com/questions/574019/… .
George Stocker

10
YAGNI argümanı her iki şekilde de alınabilir - uygulayan IDisposablebir şeyi elden çıkarmamaya karar vermek , normal en iyi uygulamaya aykırı özel bir durum olduğundan, YAGNI kapsamında gerçekten ihtiyacınız olana kadar yapmamanız gerektiğini iddia edebilirsiniz. prensip.
Jon Hanna

26

Evet bir sızıntı var , KAÇAK'ı nasıl tanımladığınıza ve ne kadar DAHA SONRA demek istediğinize bağlı olarak ...

Sızıntı ile "bellek tahsis edilmiş durumda kalır, kullanımınız bitmiş olsa bile kullanım için kullanılamaz" demektir ve ikincisi ile dispose'ı çağırdıktan sonra herhangi bir zamanda kastediyorsanız, o zaman evet kalıcı olmasa da bir sızıntı olabilir (örn. uygulamalarınızın çalışma süresi).

MemoryStream tarafından kullanılan yönetilen belleği boşaltmak için, referansı kaldırmanız gerekir geçersiz kılarak başvuruyu kaldırmanız gerekir, böylece hemen çöp toplamaya uygun hale gelir. Bunu yapmazsanız, onu kullanmayı bitirdiğiniz andan referansınız kapsam dışına çıkana kadar geçici bir sızıntı oluşturursunuz, çünkü bu arada bellek tahsis için uygun olmayacaktır.

Using ifadesinin faydası (sadece dispose çağırmaktan ziyade), using ifadesinde referansınızı BEYAN EDEBİLECEĞİNİZdir. Using ifadesi bittiğinde, yalnızca dispose çağrılır, aynı zamanda referansınız kapsam dışına çıkarak referansı etkin bir şekilde geçersiz kılar ve "reference = null" kodunu yazmanızı hatırlamanıza gerek kalmadan nesnenizi hemen çöp toplama için uygun hale getirir.

Bir şeye anında gönderme yapmamak klasik bir "kalıcı" bellek sızıntısı olmasa da, kesinlikle aynı etkiye sahiptir. Örneğin, MemoryStream referansınızı (dispose çağırdıktan sonra bile) tutarsanız ve yönteminizin biraz daha aşağısında daha fazla bellek ayırmaya çalışırsanız ... hala referans verilen bellek akışınız tarafından kullanılan bellek kullanılamayacaktır. Siz referansı iptal edene kadar veya kapsam dışına çıkana kadar, dispose'u çağırmış olsanız ve onu kullanmayı bitirmiş olsanız bile size.


6
Bu yanıtı seviyorum. Bazen insanlar kullanmanın ikili görevini unuturlar: istekli kaynak ıslahı ve istekli başvurudan vazgeçme.
Kit

1
Aslında, Java'dan farklı olarak, C # derleyicisinin "olası son kullanımı" algıladığını duymuş olsam da, bu nedenle değişken son referansından sonra kapsam dışına çıkacaksa, olası son kullanımından hemen sonra çöp toplama için uygun hale gelebilir .. . aslında kapsam dışına çıkmadan önce. Bkz stackoverflow.com/questions/680550/explicit-nulling
Triynko

2
Çöp toplayıcı ve jitter bu şekilde çalışmaz. Kapsam, bir dil yapısıdır ve çalışma zamanının uyacağı bir şey değildir. Aslında, muhtemelen blok bittiğinde .Dispose () öğesine bir çağrı ekleyerek referansın bellekte olduğu süreyi uzatıyorsunuzdur. Bkz. Ericlippert.com/2015/05/18/…
Pablo Montilla

8

Telefon etmek .Dispose()(veya sarmak Using) gerekli değildir.

Aradığınızda nedeni .Dispose()etmektir mümkün olduğunca çabuk kaynağı serbest .

Sınırlı bir bellek setimiz ve gelen binlerce isteğimizin olduğu Stack Overflow sunucusunu düşünün. Planlanmış çöp toplama için beklemek istemiyoruz, bu belleği en kısa sürede serbest bırakmak istiyoruz, böylece kullanılabilir yeni gelen istekler için.


24
Bir MemoryStream'de Dispose çağırmak, herhangi bir belleği serbest bırakmayacaktır. Aslında, Dispose'u aradıktan sonra MemoryStream'deki verileri almaya devam edebilirsiniz - deneyin :)
Jon Skeet

12
-1 MemoryStream için doğru olsa da, genel bir tavsiye olarak bu tamamen yanlıştır. Dispose, dosya tanıtıcıları veya veritabanı bağlantıları gibi yönetilmeyen kaynakları serbest bırakmaktır . Bellek bu kategoriye girmiyor. Hafızayı boşaltmak için neredeyse her zaman zamanlanmış çöp toplamayı beklemelisiniz.
Joe

1
FileStreamNesneleri tahsis etmek ve elden çıkarmak için bir kodlama stili ve nesneler için farklı bir kodlama stili benimsemenin iyi tarafı MemoryStreamnedir?
Robert Rossney

3
Bir FileStream, Dispose çağırıldığında hemen serbest bırakılabilen yönetilmeyen kaynakları içerir . Öte yandan, bir MemoryStream, yönetilen bir bayt dizisini, kullanım sırasında serbest bırakılmayan _buffer değişkeninde depolar . Aslında, _buffer, MemoryStream'in Dispose yönteminde bile boş değildir, bu bir SHAMEFUL BUG IMO'dur, çünkü referansın sıfırlanması hafızayı kullanım anında GC için uygun hale getirebilir. Bunun yerine, kalan (ancak atılmış) bir MemoryStream başvurusu hala bellekte tutulur. Bu nedenle, bir kez elden çıkardıktan sonra, hala kapsam dahilindeyse, onu da boş bırakmalısınız.
Triynko

@Triynko - "Bu nedenle, bir kez elden çıkarırsanız, hala kapsam dahilindeyse, onu da geçersiz kılmalısınız" - Katılmıyorum. Dispose çağrısından sonra tekrar kullanılırsa, bu NullReferenceException'a neden olur. Dispose'dan sonra tekrar kullanılmazsa, onu boş bırakmaya gerek yoktur; GC yeterince akıllı.
Joe

8

Bu zaten cevaplandı, ancak ekleyeceğim eski moda bilgi gizleme ilkesi, gelecekte bir noktada yeniden düzenleme yapmak isteyebileceğiniz anlamına geliyor:

MemoryStream foo()
{    
    MemoryStream ms = new MemoryStream();    
    // write stuff to ms    
    return ms;
}

için:

Stream foo()
{    
   ...
}

Bu, arayanların ne tür bir Akışın iade edildiğini umursamaması gerektiğini vurgular ve dahili uygulamayı değiştirmeyi mümkün kılar (örneğin, birim testi için alay ederken).

Bar uygulamanızda Dispose kullanmadıysanız, başınızın belaya girmesi gerekecektir:

void bar()
{    
    using (Stream s = foo())
    {
        // do stuff with s
        return;
    }
}

5

Tüm akışlar IDisposable uygular. Bellek akışınızı bir kullanım ifadesine sarın ve iyi ve şık olacaksınız. Kullanım bloğu, ne olursa olsun akışınızın kapatılmasını ve atılmasını sağlar.

Foo'yu nerede ararsanız arayın, (MemoryStream ms = foo ()) kullanarak yapabilirsiniz ve bence hala iyi olmalısınız.


1
Bu alışkanlıkla karşılaştığım bir sorun, akışın başka hiçbir yerde kullanılmadığından emin olmanız gerektiğidir. Örneğin, bir MemoryStream'i işaret eden bir JpegBitmapDecoder oluşturdum ve Çerçeveler [0] döndürdüm (verileri kendi dahili deposuna kopyalayacağını düşünerek), ancak bit eşlemin zamanın yalnızca% 20'sini göstereceğini buldum - bunun nedeni Hafıza akışını yok ediyordum.
devios1

Bellek akışınız devam etmek zorundaysa (yani bir blok kullanarak bir anlam ifade etmiyorsa), Dispose çağırmalı ve değişkeni hemen null olarak ayarlamalısınız. Eğer elden çıkarırsanız, artık kullanılması amaçlanmamıştır, bu yüzden onu hemen null olarak ayarlamalısınız. Chaiguy'un tanımladığı şey bir kaynak yönetimi sorunu gibi geliyor, çünkü ona verdiğiniz şey onu elden çıkarma sorumluluğunu almadığı ve referansı dağıtan şey artık bundan sorumlu olmadığını bilmediği sürece bir şeye referans vermemelisiniz. Bu şekilde.
Triynko

2

Bellek sızdırmazsınız, ancak kod incelemeciniz akışınızı kapatmanız gerektiğini belirtmekte haklıdır. Bunu yapmak kibarlık.

Hafızayı sızdırabileceğiniz tek durum, yanlışlıkla akışa bir referans bırakıp onu asla kapatmadığınız zamandır. Hala gerçekten bellek sızıntısı değil, ancak edilir bunu kullanıyor iddia gereksiz süreyi uzatma.


1
> Hala gerçekten bellek sızdırmıyorsunuz, ancak onu kullandığınızı iddia ettiğiniz süreyi gereksiz yere uzatıyorsunuz. Emin misiniz? Dispose, belleği serbest bırakmaz ve işlevde geç çağrılması aslında toplanamayacağı zamanı uzatabilir.
Jonathan Allen

2
Evet, Jonathan haklı. Dispose işlevine geç bir çağrı yerleştirmek, derleyicinin işlevin çok geç aşamalarında akış örneğine erişmeniz (kapatmak için) gerektiğini düşünmesine neden olabilir. Bu, dispose'ı hiç çağırmamaktan daha kötü olabilir (dolayısıyla akış değişkenine geç işlev başvurusundan kaçınılır), çünkü bir derleyici, işlevin önceki bölümlerinde bir optimal yayın noktasını (diğer bir deyişle "olası son kullanım noktası") hesaplayabilir. .
Triynko

2

MemoryStream'i esas olarak tutarlılık için bar()bir usingifadeye sarmanızı tavsiye ederim :

  • Şu anda MemoryStream, .Dispose() , ancak gelecekte bir noktada sizin veya sizin (veya şirketinizdeki başka birinin) onu kendi özel MemoryStream'inizle değiştirmesi, vb. Mümkündür.
  • Tüm Akışların elden çıkarılmasını sağlamak için projenizde bir model oluşturmanıza yardımcı olur - "Bazı Akışlar elden çıkarılmalıdır, ancak bazı akışlar zorunlu değildir" yerine "tüm Akışlar atılmalıdır" diyerek çizgi daha sıkı bir şekilde çizilir ...
  • Kodu, diğer Akış türlerinin iade edilmesine izin verecek şekilde değiştirirseniz, yine de imha etmek için değiştirmeniz gerekir.

Bir foo()IDisposable oluştururken ve döndürürken olduğu gibi genellikle yaptığım başka bir şey , nesneyi oluşturmakla nesne arasındaki herhangi bir başarısızlığın returnbir istisna tarafından yakalanmasını, nesneyi elden çıkarmasını ve istisnayı yeniden atmasını sağlamaktır:

MemoryStream x = new MemoryStream();
try
{
    // ... other code goes here ...
    return x;
}
catch
{
    // "other code" failed, dispose the stream before throwing out the Exception
    x.Dispose();
    throw;
}

1

Bir nesne IDisposable uygularsa, işiniz bittiğinde .Dispose yöntemini çağırmanız gerekir.

Bazı nesnelerde Dispose, Kapat ile aynı anlama gelir ve tersi de geçerlidir, bu durumda her ikisi de iyidir.

Şimdi, özel sorunuz için, hayır, hafıza sızdırmayacaksınız.


3
"Zorunluluk" çok güçlü bir kelimedir. Kurallar olduğunda, onları çiğnemenin sonuçlarını bilmeye değer. MemoryStream için çok az sonuç vardır.
Jon Skeet

-1

.Net uzmanı değilim, ancak belki de buradaki sorun kaynaklar, yani dosya tanıtıcısı ve bellek değil. Sanırım çöp toplayıcı sonunda akışı serbest bırakacak ve tutamacı kapatacak, ancak içeriği diske attığınızdan emin olmak için açık bir şekilde kapatmanın her zaman en iyi uygulama olacağını düşünüyorum.


Bir MemoryStream tamamen bellek içindedir - burada dosya tutacağı yoktur.
Jon Skeet

-2

Yönetilmeyen kaynakların imhası, çöp toplama dillerinde belirleyici değildir. Dispose'u açıkça çağırsanız bile, yedekleme belleğinin gerçekte ne zaman serbest bırakılacağı konusunda kesinlikle hiçbir kontrolünüz yoktur. Dispose, bir nesne kapsam dışına çıktığında, bir using deyiminden çıkarak veya bir alt yöntemden çağrı yığınını açarak, dolaylı olarak çağrılır. Bütün bunlar söyleniyor, bazen nesne aslında yönetilen bir kaynak için (örneğin dosya) bir sarmalayıcı olabilir. Bu nedenle nihayet ifadelerini açıkça kapatmak veya using ifadesini kullanmak iyi bir uygulamadır. Şerefe


1
Tam olarak doğru değil. Bir using deyiminden çıkılırken Dispose çağrılır. Dispose, bir nesne kapsam dışına çıktığında çağrılmaz.
Alexander Abramov

-3

MemorySteram, yönetilen nesne olan bayt dizisinden başka bir şey değildir. Atmayı veya kapatmayı unutun, bunun nihai sonuçlandırma dışında hiçbir yan etkisi yoktur.
Yansıtıcıdaki MemoryStream'in yapılandırıcı veya boşaltma yöntemini kontrol etmeniz yeterlidir ve iyi uygulamayı takip etmek dışında neden onu kapatmak veya elden çıkarmak konusunda endişelenmenize gerek olmadığı anlaşılacaktır.


6
-1: 4+ yıllık bir soruyu kabul edilmiş bir cevapla yayınlayacaksanız, lütfen onu faydalı bir şey yapmaya çalışın.
Tieson T.
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.