क्या एक से अधिक संसाधनों के साथ "उपयोग" एक संसाधन रिसाव का कारण बन सकता है?


106

C # मुझे निम्नलिखित करने देता है (उदाहरण MSDN से):

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

अगर font4 = new Fontफेंकता है तो क्या होता है? जो मैं समझता हूं कि फॉन्ट 3 संसाधनों को लीक करेगा और उसका निपटान नहीं किया जाएगा।

  • क्या ये सच है? (फ़ॉन्ट 4 का निपटान नहीं किया जाएगा)
  • क्या इसका मतलब using(... , ...)नेस्टेड उपयोग के पक्ष में पूरी तरह से बचा जाना चाहिए?

7
यह स्मृति को लीक नहीं करेगा ; सबसे बुरे मामले में, यह अभी भी GC'd मिलेगा।
SLAKs

3
मुझे आश्चर्य नहीं होगा यदि using(... , ...)ब्लॉक की परवाह किए बिना नेस्टेड में संकलित किया जाता है, लेकिन मुझे यह नहीं पता है कि निश्चित रूप से।
दान जे

1
मेरा वह मतलब नहीं था। यहां तक ​​कि अगर आप बिल्कुल भी उपयोग नहीं करते usingहैं, तो जीसी अंततः इसे इकट्ठा करेगा।
SLACs

1
@zneak: अगर यह एक ही finallyब्लॉक में संकलित होता, तो यह ब्लॉक में तब तक प्रवेश नहीं करता, जब तक कि सभी संसाधनों का निर्माण नहीं हो जाता।
SLKs

2
@zneak: क्योंकि usinga try- के रूपांतरण में finally, इनिशियलाइज़ेशन एक्सप्रेशन का मूल्यांकन बाहर किया जाता है try। तो यह एक वाजिब सवाल है।
बेन वोइग्ट

जवाबों:


158

नहीं।

कंपाइलर finallyप्रत्येक चर के लिए एक अलग ब्लॉक उत्पन्न करेगा ।

कल्पना (§8.13) का कहना है:

जब एक संसाधन-अधिग्रहण स्थानीय-चर-घोषणा का रूप लेता है, तो किसी दिए गए प्रकार के कई संसाधनों को प्राप्त करना संभव है। usingफार्म का एक बयान

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

कथन का उपयोग करके नेस्टेड अनुक्रम के बराबर है:

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

4
यह C # स्पेसिफिकेशन वर्जन 5.0, btw में 8.13 है।
बेन वोइग्ट

11
@WeylandYutani: आप क्या पूछ रहे हैं?
SLKs

9
@WeylandYutani: यह एक सवाल-जवाब की साइट है। यदि आपके पास कोई प्रश्न है, तो कृपया एक नया प्रश्न शुरू करें!
एरिक लिपर्ट

5
@ user1306322 क्यों? क्या होगा यदि मैं वास्तव में जानना चाहता हूं?
ऑक्सीमोरन

2
@Oxymoron तो आपको शोध और अनुमान के रूप में प्रश्न पोस्ट करने से पहले प्रयास के कुछ सबूत प्रदान करने चाहिए, या फिर आपको वही बताया जाएगा, ध्यान खोना और अन्यथा अधिक नुकसान में होना चाहिए। व्यक्तिगत अनुभव के आधार पर बस एक सलाह।
user1306322

67

अद्यतन : मैंने इस प्रश्न को एक लेख के आधार के रूप में उपयोग किया है जो यहां पाया जा सकता है ; इसे इस मुद्दे की अतिरिक्त चर्चा के लिए देखें। अच्छे प्रश्न के लिए धन्यवाद!


हालांकि शबसे का उत्तर निश्चित रूप से सही है और पूछे गए प्रश्न का उत्तर देता है, आपके प्रश्न पर एक महत्वपूर्ण संस्करण है जो आपने नहीं किया है:

यदि निर्माणकर्ता द्वारा अप्रबंधित संसाधन आवंटित किए जाने के बादfont4 = new Font() फेंकता है तो क्या होता है, लेकिन संदर्भ से पहले ही ctor रिटर्न और भरता है ?font4

मैं इसे थोड़ा और स्पष्ट कर दूं। मान लीजिए हमारे पास:

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;
        }
    }
}

अब हमारे पास है

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

यह भी ऐसा ही है

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

ठीक है। मान Whateverलो फेंकता है। फिर finallyब्लॉक चलता है और संसाधन समाप्त हो जाता है। कोई दिक्कत नहीं है।

मान Blah1()लो फेंकता है। फिर थ्रो संसाधन के आवंटित होने से पहले होता है। ऑब्जेक्ट को आवंटित किया गया है लेकिन ctor कभी नहीं लौटता है, इसलिए fooकभी भी भरा नहीं जाता है। हमने कभी भी प्रवेश नहीं किया है tryइसलिए हम कभी भी प्रवेश नहीं करते finallyहैं। वस्तु संदर्भ अनाथ कर दिया गया है। आखिरकार जीसी की खोज की जाएगी और इसे अंतिम कतार में रखा जाएगा। handleअभी भी शून्य है, इसलिए फाइनल कुछ नहीं करता है। ध्यान दें कि अंतिम रूप से उस वस्तु के सामने मजबूत होना आवश्यक है जिसे अंतिम रूप दिया जा रहा है जिसका निर्माता कभी पूरा नहीं हुआ । आपको अंतिम रूप से लिखने की आवश्यकता है जो यह मजबूत हो। यह अभी तक एक और कारण है कि आपको विशेषज्ञों को अंतिम रूप देना चाहिए और इसे स्वयं करने का प्रयास नहीं करना चाहिए।

मान लीजिए Blah3() लो फेंकता है। संसाधन आवंटित होने के बाद थ्रो होता है। लेकिन फिर, fooकभी भी भरा नहीं जाता है, हम कभी भी प्रवेश नहीं करते हैंfinally , और ऑब्जेक्ट को अंतिम थ्रेड द्वारा साफ किया जाता है। इस बार हैंडल नॉन-जीरो है, और फाइनली इसे साफ करता है। फ़ाइनलीज़र फिर से एक ऐसी वस्तु पर चल रहा है जिसका निर्माता कभी सफल नहीं हुआ, लेकिन फ़ाइनलीज़र वैसे भी चलता है। जाहिर है कि इस बार क्योंकि यह काम करना था।

अब मान Blah2()लो फेंकता है। थ्रो संसाधन के आबंटित होने के बाद लेकिन पहले होता है handle भरने ! फिर से, फाइनल चलेगा लेकिन अब handleभी शून्य है और हम हैंडल को लीक कर रहे हैं!

इस रिसाव को होने से रोकने के लिए आपको बेहद चतुर कोड लिखना होगा । अब, अपने Fontसंसाधन के मामले में , कौन परवाह करता है? हम एक फ़ॉन्ट संभाल, बड़ी बात लीक करते हैं। लेकिन अगर आपको पूरी तरह से सकारात्मक रूप से आवश्यकता है कि हर अप्रबंधित संसाधन को साफ किया जाए तो कोई फर्क नहीं पड़ता कि अपवादों का समय क्या है, तो आपके हाथों में एक बहुत मुश्किल समस्या है।

सीएलआर को ताले के साथ इस समस्या को हल करना होगा। C # 4 के बाद से, lockकथन का उपयोग करने वाले ताले इस तरह से लागू किए गए हैं:

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

Enterबहुत सावधानी से लिखा गया है ताकि कोई फर्क नहीं पड़ता कि क्या अपवाद हैं , lockEnteredयह निर्धारित करने के लिए सही है कि क्या और केवल अगर ताला वास्तव में लिया गया था। यदि आपके पास समान आवश्यकताएं हैं, तो आपको वास्तव में जो लिखना है, वह है:

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

और AllocateResourceचतुराई से लिखो Monitor.Enterताकि कोई फर्क नहीं पड़ता कि अंदर क्या होता है AllocateResource, अगर और केवल अगरhandle में भरा है उसे निपटाया जाए।

ऐसा करने के लिए तकनीकों का वर्णन करना इस उत्तर के दायरे से परे है। यदि आपके पास यह आवश्यकता है, तो एक विशेषज्ञ से परामर्श करें।


6
@ नागट: स्वीकृत उत्तर। कि S को किसी चीज के लिए खड़ा होना है। :-)
एरिक लिपर्ट

12
@ जो: बेशक उदाहरण से वंचित हैमैं बस इससे वंचित रहा । जोखिम अतिरंजित नहीं हैं क्योंकि मैंने यह नहीं बताया है कि जोखिम का स्तर क्या है; बल्कि, मैंने कहा है कि यह पैटर्न संभव है । यह तथ्य कि आप मानते हैं कि क्षेत्र को सीधे सेट करना समस्या का हल करता है, ठीक इसी तरह से मेरी बात को इंगित करता है: जैसे कि अधिकांश प्रोग्रामर जिन्हें इस तरह की समस्या का कोई अनुभव नहीं है, आप इस समस्या को हल करने के लिए सक्षम नहीं हैं; वास्तव में, ज्यादातर लोगों को भी पहचान नहीं पा रहे है कि वहाँ है एक समस्या है, जो कारण है कि मैं पहली जगह में इस जवाब लिखा
एरिक लिपर्ट

5
@ क्रिस: मान लीजिए कि आवंटन और रिटर्न के बीच और रिटर्न और असाइनमेंट के बीच शून्य कार्य होता है। हम उन सभी Blahमेथड कॉल को हटा देते हैं। उन बिंदुओं में से किसी एक पर थ्रेडअर्बसेप्शन को रोकने से क्या होता है?
एरिक लिपर्ट

5
@ जो: यह एक बहस करने वाला समाज नहीं है; मैं अधिक आश्वस्त होकर स्कोर अंक नहीं देख रहा हूं । यदि आपको संदेह है और आप इसके लिए मेरा शब्द नहीं लेना चाहते हैं कि यह एक मुश्किल समस्या है, जिसे सही ढंग से हल करने के लिए विशेषज्ञों से परामर्श की आवश्यकता है तो आप मुझसे असहमत हैं।
एरिक लिपर्ट

7
@ गिल्स रॉबर्ट्स: यह समस्या का समाधान कैसे करता है? मान लीजिए कि अपवाद कॉल के बाद होता है, AllocateResourceलेकिन असाइनमेंट से पहलेx । A ThreadAbortExceptionउस बिंदु पर हो सकता है। यहां सभी को मेरी बात याद आ रही है, जो एक संसाधन का निर्माण है और एक चर के संदर्भ का असाइनमेंट परमाणु ऑपरेशन नहीं है । आपकी पहचान की समस्या को हल करने के लिए आपको इसे एक परमाणु ऑपरेशन बनाना होगा।
एरिक लिपर्ट

32

@SLaks उत्तर के पूरक के रूप में, यहाँ आपके कोड के लिए IL है:

.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

नेस्टेड प्रयास को ध्यान दें / अंत में ब्लॉक करें।


17

यह कोड (मूल नमूने के आधार पर):

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.
        }
    }
}

यह निम्न CIL ( Visual Studio 2013 में , .NET 4.5.1 को लक्षित करता है) उत्पन्न करता है :

.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

जैसा कि आप देख सकते हैं, try {}पहले आवंटन के बाद ब्लॉक शुरू नहीं होता है, जो कि जगह लेता है IL_0012। पहली नज़र में, यह असुरक्षित कोड में पहले आइटम को आवंटित करने के लिए प्रकट होता है । हालाँकि, ध्यान दें कि परिणाम स्थान 0 में संग्रहीत है। यदि दूसरा आवंटन विफल हो जाता है, तो बाहरी finally {} ब्लॉक निष्पादित होता है, और यह ऑब्जेक्ट को स्थान 0 से ले जाता है, अर्थात का पहला आवंटन font3, और इसकी Dispose()विधि को कॉल करता है।

दिलचस्प है, dotPeek के साथ इस विधानसभा को विघटित करने से निम्नलिखित पुनर्गठित स्रोत का उत्पादन होता है:

using System.Drawing;

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

विघटित कोड यह पुष्टि करता है कि सब कुछ सही है और usingअनिवार्य रूप से नेस्टेड usingएस में विस्तारित है । CIL कोड देखने में थोड़ा भ्रमित करने वाला है, और मुझे कुछ मिनटों के लिए घूरना पड़ा, इससे पहले कि मुझे ठीक से समझ में आ जाए कि क्या हो रहा है, इसलिए मुझे आश्चर्य नहीं है कि कुछ w पुरानी पत्नियों की कहानियां ’उछलने लगी हैं यह। हालाँकि, उत्पन्न कोड असत्य सत्य है।


@Peter आपके संपादन को IL कोड (IL_0012 और IL_0017 के बीच) के हटाए गए विखंडू को अमान्य और भ्रामक दोनों तरह से व्याख्या प्रदान करता है। उस कोड का उद्देश्य मेरे द्वारा प्राप्त किए गए और संपादित किए गए परिणामों की एक शब्दशः प्रतिलिपि होना था । क्या आप कृपया अपने संपादन की समीक्षा कर सकते हैं और इसकी पुष्टि कर सकते हैं कि आपका इरादा क्या है?
टिम लॉन्ग

7

यहाँ @SLaks उत्तर को साबित करने के लिए एक नमूना कोड दिया गया है:

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
यह साबित नहीं करता है। डिस्पोज़ कहां है: t2? :)
पायोत्र पेरक

1
सवाल प्रयोग सूची पर पहले संसाधन के निपटान के बारे में है दूसरा नहीं। "क्या होता है अगर font4 = new Fontफेंकता है? जो मैं समझता हूं कि फॉन्ट 3 संसाधनों को लीक करेगा और उसका निपटान नहीं किया जाएगा।"
wdosanjos
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.