Birden fazla kaynakla "kullanmak" bir kaynak sızıntısına neden olabilir mi?


106

C # şunu yapmama izin veriyor (MSDN'den örnek):

using (Font font3 = new Font("Arial", 10.0f),
            font4 = new Font("Arial", 10.0f))
{
    // Use font3 and font4.
}

font4 = new FontFırlatırsa ne olur ? Anladığım kadarıyla font3 kaynakları sızdıracak ve imha edilmeyecek.

  • Bu doğru mu? (font4 atılmayacak)
  • Bu using(... , ...), iç içe kullanım lehine tamamen kaçınılması gerektiği anlamına mı geliyor ?

7
Bu olmaz sızıntı bellek; en kötü durumda, yine de GC'lenir.
SLaks

3
Ne using(... , ...)olursa olsun, bloklar kullanılarak iç içe geçmiş şekilde derlenirse şaşırmam , ama bundan emin değilim.
Dan J

1
Demek istediğim bu değil. Hiç kullanmasanız bile using, GC yine de sonunda toplayacaktır.
SLaks

1
@zneak: Tek bir finallyblokta derlenmiş olsaydı , tüm kaynaklar inşa edilene kadar bloğa girmezdi .
SLaks

2
@zneak: Çünkü dönüştürülmesinde usinga try- finally, başlatma ifade dışında değerlendirilir try. Bu yüzden makul bir soru.
Ben Voigt

Yanıtlar:


158

Hayır.

Derleyici, finallyher değişken için ayrı bir blok oluşturacaktır .

Spec (§8.13) der ki:

Bir kaynak edinimi bir yerel değişken bildirimi biçimini aldığında, belirli bir türden birden çok kaynağı elde etmek mümkündür. usingFormun bir açıklaması

using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement 

tam olarak iç içe geçmiş ifadeler dizisine eşdeğerdir:

using (ResourceType r1 = e1)
   using (ResourceType r2 = e2)
      ...
         using (ResourceType rN = eN)
            statement

4
Bu, C # Specification sürüm 5.0, btw'de 8.13'tür.
Ben Voigt

11
@WeylandYutani: Ne soruyorsun?
SLaks

9
@WeylandYutani: Bu bir soru-cevap sitesi. Bir sorunuz varsa, lütfen yeni bir soru başlatın!
Eric Lippert

5
@ user1306322 neden? Ya gerçekten bilmek istersem?
Oxymoron

2
@Oxymoron, soruyu araştırma ve tahmin şeklinde göndermeden önce biraz çaba göstermelisiniz, aksi takdirde aynı şey söylenir, dikkatinizi kaybedersiniz ve aksi takdirde daha büyük bir kayıp yaşarsınız. Kişisel deneyime dayalı bir tavsiye.
user1306322

67

GÜNCELLEME : Ben bulunabilir bir makale için temel olarak bu soruyu kullanılan burada ; bu konuyla ilgili ek tartışma için buna bakın. Güzel soru için teşekkürler!


Schabse'nin cevabı elbette doğru olsa ve sorulan soruyu cevaplasa da, sorunuzda sormadığınız önemli bir varyant var:

Yönetilmeyen kaynak kurucu tarafından tahsis edildikten sonra , ancak ctor geri dönüp referansla doldurmadan öncefont4 = new Font() atışlar ne olur ?font4

Bunu biraz daha netleştireyim. Varsayalım ki:

public sealed class Foo : IDisposable
{
    private int handle = 0;
    private bool disposed = false;
    public Foo()
    {
        Blah1();
        int x = AllocateResource();
        Blah2();
        this.handle = x;
        Blah3();
    }
    ~Foo()
    {
        Dispose(false);
    }
    public void Dispose() 
    { 
        Dispose(true); 
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (this.handle != 0) 
                DeallocateResource(this.handle);
            this.handle = 0;
            this.disposed = true;
        }
    }
}

Şimdi sahibiz

using(Foo foo = new Foo())
    Whatever(foo);

Bu aynıdır

{
    Foo foo = new Foo();
    try
    {
        Whatever(foo);
    }
    finally
    {
        IDisposable d = foo as IDisposable;
        if (d != null) 
            d.Dispose();
    }
}

TAMAM. Atışları varsayalım Whatever. Daha sonra finallyblok çalışır ve kaynak serbest bırakılır. Sorun değil.

Atışları varsayalım Blah1(). Ardından, kaynak ayrılmadan önce atış gerçekleşir. Nesne tahsis edilmiştir, ancak ctor asla geri dönmez, bu yüzden fooasla doldurulmaz. Asla girmedik, trybu yüzden finallyikisine de girmeyiz. Nesne referansı artık kaldı. Sonunda GC bunu keşfedecek ve onu sonlandırıcı sırasına koyacaktır. handlehala sıfırdır, bu nedenle sonlandırıcı hiçbir şey yapmaz. Sonlandırıcının, kurucusu hiçbir zaman tamamlanmayan, sonuçlandırılan bir nesne karşısında sağlam olması gerektiğine dikkat edin . Sen edilir gerekli bu güçlü finalizers yazmak için. Bu, son yazıyı uzmanlara bırakmanız ve bunu kendiniz yapmaya çalışmamanız için bir başka nedendir.

Atışları varsayalım Blah3(). Kaynak tahsis edildikten sonra atış gerçekleşir. Ama yine, fooasla doldurulmaz, asla girilmez finallyve nesne sonlandırıcı ipliği tarafından temizlenir. Bu sefer tutamaç sıfır değildir ve sonlandırıcı onu temizler. Yine, sonlandırıcı, kurucusu asla başarılı olamayan bir nesne üzerinde çalışıyor, ancak sonlandırıcı yine de çalışıyor. Açıkçası olmalı çünkü bu sefer yapacak işleri vardı.

Şimdi Blah2()atışları varsayalım . Atış, kaynak tahsis edildikten sonra ancak doldurulmadan önce handle gerçekleşir! Yine, sonlandırıcı çalışacak ama şimdi handlehala sıfır ve kolu sızdırıyoruz!

Bu sızıntının olmasını önlemek için son derece akıllı bir kod yazmanız gerekir . Şimdi, Fontkaynağınız söz konusu olduğunda , kimin umurunda? Bir yazı tipi tanıtıcısı sızdırıyoruz, önemli. Ama eğer kesinlikle olumlu gerektiren bu her yönetilmeyen kaynak temizlenecek olursa olsun istisna zamanlaması ne o zaman ellerini çok zor bir sorun var.

CLR bu sorunu kilitlerle çözmelidir. C # 4'ten beri, lockifadeyi kullanan kilitler şu şekilde uygulanmıştır:

bool lockEntered = false;
object lockObject = whatever;
try
{
    Monitor.Enter(lockObject, ref lockEntered);
    lock body here
}
finally
{
    if (lockEntered) Monitor.Exit(lockObject);
}

Enterçok dikkatli bir şekilde yazılmıştır, böylece hangi istisnalar yapılırsa yapılsın , ancak ve ancak kilit gerçekten alınmışsa lockEntereddoğru olarak ayarlanır . Benzer gereksinimleriniz varsa, yapmanız gereken şey aslında yazmaktır:

    public Foo()
    {
        Blah1();
        AllocateResource(ref handle);
        Blah2();
        Blah3();
    }

ve AllocateResourcezekice yazın Monitor.Enterki içeride ne olursa olsun AllocateResource, sadece ve ancak ayrılması gerektiğinde handledoldurulur .

Bunu yapmak için gereken teknikleri açıklamak bu cevabın kapsamı dışındadır. Bu gereksiniminiz varsa bir uzmana danışın.


6
@gnat: Kabul edilen cevap. S'nin bir şeyi ifade etmesi gerekiyor. :-)
Eric Lippert

12
@Joe: Elbette örnek olduğunu yapmacık . Ben sadece uydurdum . Riskler abartılı değil çünkü risk seviyesinin ne olduğunu belirtmedim ; bunun yerine, bu modelin mümkün olduğunu belirttim . Alanın doğrudan sorunu çözdüğüne inandığınız gerçeği, tam da benim amacıma işaret ediyor: bu tür bir problemle hiç deneyimi olmayan programcıların büyük çoğunluğu gibi, bu sorunu çözme konusunda yetkin değilsiniz; Gerçekten de, çoğu insan bile orada olduğunu fark yok olduğunu hangi bir sorun ben ilk etapta bu cevabı yazdım .
Eric Lippert

5
@Chris: Tahsis ve iade arasında ve iade ile atama arasında sıfır iş yapıldığını varsayalım. Tüm bu Blahyöntem çağrılarını siliyoruz. Bir ThreadAbortException'ın bu noktalardan herhangi birinde olmasını ne durdurur?
Eric Lippert

5
@Joe: Bu bir tartışma topluluğu değil; Daha inandırıcı olarak puan kazanmak istemiyorum . Şüpheliyseniz ve bunun doğru bir şekilde çözülmesi için uzmanlara danışmayı gerektiren zor bir sorun olduğuna dair sözüme güvenmek istemiyorsanız, o zaman benimle aynı fikirde olmayabilirsiniz.
Eric Lippert

7
@GilesRoberts: Bu sorunu nasıl çözüyor? İstisna olur varsayalım sonra yapılan çağrı AllocateResourceama önce hiç atama x. ThreadAbortExceptionBu noktada bir olabilir. Buradaki herkes benim açımdan özlüyor gibi görünüyor, bu da bir kaynağın oluşturulması ve ona bir değişkene referans verilmesi atomik bir işlem değil . Tanımladığım problemi çözmek için onu atomik bir operasyon yapmalısınız.
Eric Lippert

32

@SLaks yanıtına bir tamamlayıcı olarak, kodunuz için IL burada:

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 74 (0x4a)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] class [System.Drawing]System.Drawing.Font font3,
        [1] class [System.Drawing]System.Drawing.Font font4,
        [2] bool CS$4$0000
    )

    IL_0000: nop
    IL_0001: ldstr "Arial"
    IL_0006: ldc.r4 10
    IL_000b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
    IL_0010: stloc.0
    .try
    {
        IL_0011: ldstr "Arial"
        IL_0016: ldc.r4 10
        IL_001b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
        IL_0020: stloc.1
        .try
        {
            IL_0021: nop
            IL_0022: nop
            IL_0023: leave.s IL_0035
        } // end .try
        finally
        {
            IL_0025: ldloc.1
            IL_0026: ldnull
            IL_0027: ceq
            IL_0029: stloc.2
            IL_002a: ldloc.2
            IL_002b: brtrue.s IL_0034

            IL_002d: ldloc.1
            IL_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose()
            IL_0033: nop

            IL_0034: endfinally
        } // end handler

        IL_0035: nop
        IL_0036: leave.s IL_0048
    } // end .try
    finally
    {
        IL_0038: ldloc.0
        IL_0039: ldnull
        IL_003a: ceq
        IL_003c: stloc.2
        IL_003d: ldloc.2
        IL_003e: brtrue.s IL_0047

        IL_0040: ldloc.0
        IL_0041: callvirt instance void [mscorlib]System.IDisposable::Dispose()
        IL_0046: nop

        IL_0047: endfinally
    } // end handler

    IL_0048: nop
    IL_0049: ret
} // end of method Program::Main

İç içe geçmiş dene / nihayet bloklarına dikkat edin.


17

Bu kod (orijinal örneğe göre):

using System.Drawing;

public class Class1
{
    public Class1()
    {
        using (Font font3 = new Font("Arial", 10.0f),
                    font4 = new Font("Arial", 10.0f))
        {
            // Use font3 and font4.
        }
    }
}

Aşağıdaki CIL'i üretir ( Visual Studio 2013'te .NET 4.5.1'i hedefler ):

.method public hidebysig specialname rtspecialname
        instance void  .ctor() cil managed
{
    // Code size       82 (0x52)
    .maxstack  2
    .locals init ([0] class [System.Drawing]System.Drawing.Font font3,
                  [1] class [System.Drawing]System.Drawing.Font font4,
                  [2] bool CS$4$0000)
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  nop
    IL_0008:  ldstr      "Arial"
    IL_000d:  ldc.r4     10.
    IL_0012:  newobj     instance void [System.Drawing]System.Drawing.Font::.ctor(string,
                                                                                  float32)
    IL_0017:  stloc.0
    .try
    {
        IL_0018:  ldstr      "Arial"
        IL_001d:  ldc.r4     10.
        IL_0022:  newobj     instance void [System.Drawing]System.Drawing.Font::.ctor(string,
                                                                                      float32)
        IL_0027:  stloc.1
        .try
        {
            IL_0028:  nop
            IL_0029:  nop
            IL_002a:  leave.s    IL_003c
        }  // end .try
        finally
        {
            IL_002c:  ldloc.1
            IL_002d:  ldnull
            IL_002e:  ceq
            IL_0030:  stloc.2
            IL_0031:  ldloc.2
            IL_0032:  brtrue.s   IL_003b
            IL_0034:  ldloc.1
            IL_0035:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
            IL_003a:  nop
            IL_003b:  endfinally
        }  // end handler
        IL_003c:  nop
        IL_003d:  leave.s    IL_004f
    }  // end .try
    finally
    {
        IL_003f:  ldloc.0
        IL_0040:  ldnull
        IL_0041:  ceq
        IL_0043:  stloc.2
        IL_0044:  ldloc.2
        IL_0045:  brtrue.s   IL_004e
        IL_0047:  ldloc.0
        IL_0048:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
        IL_004d:  nop
        IL_004e:  endfinally
    }  // end handler
    IL_004f:  nop
    IL_0050:  nop
    IL_0051:  ret
} // end of method Class1::.ctor

Gördüğünüz gibi, try {}blok, 'da gerçekleşen ilk tahsisin sonrasına kadar başlamıyor IL_0012. İlk bakışta bu, korumasız koddaki ilk öğeyi tahsis ediyor gibi görünüyor . Bununla birlikte, sonucun konum 0'da saklandığına dikkat edin. İkinci tahsis başarısız olursa, dış finally {} blok çalışır ve bu, nesneyi 0 konumundan, yani ilk tahsisinden alır font3ve Dispose()yöntemini çağırır .

İlginç bir şekilde, bu derlemeyi dotPeek ile yeniden derlemek , aşağıdaki yeniden oluşturulmuş kaynağı üretir:

using System.Drawing;

public class Class1
{
    public Class1()
    {
        using (new Font("Arial", 10f))
        {
            using (new Font("Arial", 10f))
                ;
        }
    }
}

Derlenmiş kod, her şeyin doğru olduğunu ve usingesasen iç içe geçmiş usinge- postalara genişletildiğini doğrular . CIL koduna bakmak biraz kafa karıştırıcı ve ne olduğunu tam olarak anlamadan önce ona birkaç dakika bakmak zorunda kaldım, bu yüzden bazı 'eski eş hikayelerinin' filizlenmeye başlamasına şaşırmadım. bu. Ancak üretilen kod tartışılmaz bir gerçektir.


@Peter Mortensen, düzenlemeniz IL kodunun parçalarını kaldırdı (IL_0012 ve IL_0017 arasında) açıklamayı hem geçersiz hem de kafa karıştırıcı hale getirdi. Bu kod, elde ettiğim sonuçların birebir kopyası olarak tasarlandı ve düzenleme bunu geçersiz kılar. Lütfen düzenlemenizi gözden geçirip amacınızın bu olduğunu onaylar mısınız?
Tim Long

7

İşte @SLaks yanıtını kanıtlamak için örnek bir kod:

void Main()
{
    try
    {
        using (TestUsing t1 = new TestUsing("t1"), t2 = new TestUsing("t2"))
        {
        }
    }
    catch(Exception ex)
    {
        Console.WriteLine("catch");
    }
    finally
    {
        Console.WriteLine("done");
    }

    /* outputs

        Construct: t1
        Construct: t2
        Dispose: t1
        catch
        done

    */
}

public class TestUsing : IDisposable
{
    public string Name {get; set;}

    public TestUsing(string name)
    {
        Name = name;

        Console.WriteLine("Construct: " + Name);

        if (Name == "t2") throw new Exception();
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose: " + Name);
    }
}

1
Bu kanıtlamaz. Dispose nerede: t2? :)
Piotr Perak

1
Soru, ikinci kaynağın kullanım listesindeki ilk kaynağın elden çıkarılmasıyla ilgilidir. " font4 = new FontAtarsa ne olur ? Anladığım kadarıyla font3 kaynakları sızdıracak ve bertaraf edilmeyecek."
wdosanjos
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.