2010-05-14 17 views
37

मुझे लगता है कि यह एक दिलचस्प कोड उदाहरण है।लॉक स्टेटमेंट बनाम मॉनीटर। विधि

हम एक वर्ग है - एक अंतिम रूप विधि के साथ - यह टेस्ट फोन करते हैं। मुख्य विधि में दो कोड ब्लॉक हैं जहां मैं लॉक स्टेटमेंट और मॉनिटर का उपयोग कर रहा हूं। एंटर() कॉल करें। इसके अलावा, मेरे पास टेस्ट कक्षा के दो उदाहरण हैं। प्रयोग बहुत आसान है: परीक्षण लॉकिंग ब्लॉक के भीतर परिवर्तनीय और फिर इसे जीसी के साथ मैन्युअल रूप से एकत्र करने का प्रयास करें। विधि कॉल का चयन करें। तो, को देखने के लिए कॉल को अंतिम रूप दें, मैं GC.WaitForPendingFinalizers विधि को कॉल कर रहा हूं। सब कुछ बहुत आसान है, जैसा कि आप देख सकते हैं।

ताला बयान की परिभाषा के अनुसार, यह के अंदर एक Monitor.Enter कॉल के साथ को संकलक द्वारा खोला है {...} अंत में {} .. ब्लॉक की कोशिश, ब्लॉक और पर आज़माएं। फिर यह अंततः ब्लॉक में निकलता है। मैंने को मैन्युअल रूप से ब्लॉक को लागू करने का प्रयास किया है।

मुझे दोनों मामलों में एक ही व्यवहार की उम्मीद है - लॉक का उपयोग करने और मॉनीटर का उपयोग करने के लिए। दर्ज करें। लेकिन, आश्चर्य, आश्चर्य यह अलग है, जैसा कि आप नीचे देख सकते हैं:

public class Test 
{ 
    private string name; 

    public Test(string name) 
    { 
     this.name = name; 
    } 

    ~Test() 
    { 
     Console.WriteLine(string.Format("Finalizing class name {0}.", name)); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var test1 = new Test("Test1"); 
     var test2 = new Test("Tesst2"); 
     lock (test1) 
     { 
      test1 = null; 
      Console.WriteLine("Manual collect 1."); 
      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
      Console.WriteLine("Manual collect 2."); 
      GC.Collect(); 
     } 

     var lockTaken = false; 
     System.Threading.Monitor.Enter(test2, ref lockTaken); 
     try { 
      test2 = null; 
      Console.WriteLine("Manual collect 3."); 
      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
      Console.WriteLine("Manual collect 4."); 
      GC.Collect(); 
     } 
     finally { 
      System.Threading.Monitor.Exit(test2); 
     } 
     Console.ReadLine(); 
    } 
} 

इस उदाहरण के उत्पादन में है:

मैनुअल इकट्ठा 1. मैनुअल इकट्ठा 2. मैनुअल 3. अंतिम रूप दिया जा वर्ग इकट्ठा नाम टेस्ट 2। मैन्युअल संग्रह 4. और अंतिम अंत में ब्लॉक में अंतिम संदर्भ अपवाद क्योंकि test2 शून्य संदर्भ है।

मैं अपने कोड को आईएल में आश्चर्यचकित और अलग कर रहा था।

.entrypoint 
.maxstack 2 
.locals init (
    [0] class ConsoleApplication2.Test test1, 
    [1] class ConsoleApplication2.Test test2, 
    [2] bool lockTaken, 
    [3] bool <>s__LockTaken0, 
    [4] class ConsoleApplication2.Test CS$2$0000, 
    [5] bool CS$4$0001) 
L_0000: nop 
L_0001: ldstr "Test1" 
L_0006: newobj instance void ConsoleApplication2.Test::.ctor(string) 
L_000b: stloc.0 
L_000c: ldstr "Tesst2" 
L_0011: newobj instance void ConsoleApplication2.Test::.ctor(string) 
L_0016: stloc.1 
L_0017: ldc.i4.0 
L_0018: stloc.3 
L_0019: ldloc.0 
L_001a: dup 
L_001b: stloc.s CS$2$0000 
L_001d: ldloca.s <>s__LockTaken0 
L_001f: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&) 
L_0024: nop 
L_0025: nop 
L_0026: ldnull 
L_0027: stloc.0 
L_0028: ldstr "Manual collect." 
L_002d: call void [mscorlib]System.Console::WriteLine(string) 
L_0032: nop 
L_0033: call void [mscorlib]System.GC::Collect() 
L_0038: nop 
L_0039: call void [mscorlib]System.GC::WaitForPendingFinalizers() 
L_003e: nop 
L_003f: ldstr "Manual collect." 
L_0044: call void [mscorlib]System.Console::WriteLine(string) 
L_0049: nop 
L_004a: call void [mscorlib]System.GC::Collect() 
L_004f: nop 
L_0050: nop 
L_0051: leave.s L_0066 
L_0053: ldloc.3 
L_0054: ldc.i4.0 
L_0055: ceq 
L_0057: stloc.s CS$4$0001 
L_0059: ldloc.s CS$4$0001 
L_005b: brtrue.s L_0065 
L_005d: ldloc.s CS$2$0000 
L_005f: call void [mscorlib]System.Threading.Monitor::Exit(object) 
L_0064: nop 
L_0065: endfinally 
L_0066: nop 
L_0067: ldc.i4.0 
L_0068: stloc.2 
L_0069: ldloc.1 
L_006a: ldloca.s lockTaken 
L_006c: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&) 
L_0071: nop 
L_0072: nop 
L_0073: ldnull 
L_0074: stloc.1 
L_0075: ldstr "Manual collect." 
L_007a: call void [mscorlib]System.Console::WriteLine(string) 
L_007f: nop 
L_0080: call void [mscorlib]System.GC::Collect() 
L_0085: nop 
L_0086: call void [mscorlib]System.GC::WaitForPendingFinalizers() 
L_008b: nop 
L_008c: ldstr "Manual collect." 
L_0091: call void [mscorlib]System.Console::WriteLine(string) 
L_0096: nop 
L_0097: call void [mscorlib]System.GC::Collect() 
L_009c: nop 
L_009d: nop 
L_009e: leave.s L_00aa 
L_00a0: nop 
L_00a1: ldloc.1 
L_00a2: call void [mscorlib]System.Threading.Monitor::Exit(object) 
L_00a7: nop 
L_00a8: nop 
L_00a9: endfinally 
L_00aa: nop 
L_00ab: call string [mscorlib]System.Console::ReadLine() 
L_00b0: pop 
L_00b1: ret 
.try L_0019 to L_0053 finally handler L_0053 to L_0066 
.try L_0072 to L_00a0 finally handler L_00a0 to L_00aa 

मैं ताला बयान और Monitor.Enter कॉल के बीच कोई अंतर नहीं दिख रहा है: तो, यहाँ मुख्य विधि के आईएल डंप है। तो, क्यों मैं अभी भी ताला के मामले में test1 के कहने के लिए एक संदर्भ के लिए है, और वस्तु जीसी द्वारा एकत्र नहीं है, लेकिन का उपयोग कर Monitor.Enter के मामले में इसे एकत्र और अंतिम रूप दे दिया गया है ?

उत्तर

18

ऐसा इसलिए है क्योंकि test1 द्वारा संदर्भित संदर्भ आईएल कोड में स्थानीय चर CS$2$0000 को सौंपा गया है। आप सी # में test1 वैरिएबल को हटाते हैं, लेकिन lock निर्माण इस तरह से संकलित हो जाता है कि एक अलग संदर्भ बनाए रखा जाता है।

यह वास्तव में काफी चालाक सी # संकलक करता है। अन्यथा यह गारंटी lock बयान महत्वपूर्ण अनुभाग बाहर निकलने पर ताला जारी करने की लागू करने के लिए माना जाता है नाकाम करने के लिए संभव हो जाएगा।

+2

हाँ। उपयोग कथन भी इस तरह से काम करता है। –

77

मुझे लॉक स्टेटमेंट और मॉनिटर के बीच कोई अंतर नहीं दिखता है। कॉल दर्ज करें।

अधिक ध्यान से देखो। पहला मामला यह सुनिश्चित करने के लिए कि यह जीवित रहता है, दूसरे स्थानीय चर के संदर्भ की प्रतिलिपि बनाता है।

सूचना क्या सी # 3.0 कल्पना विषय पर कहते हैं:

प्रपत्र की एक ताला बयान "ताला (x) ..." जहां एक्स एक संदर्भ प्रकार की अभिव्यक्ति है, ठीक बराबर है

System.Threading.Monitor.Enter(x); 
try { ... } 
finally { System.Threading.Monitor.Exit(x); } 

सिवाय इसके कि एक्स के लिए केवल एक बार मूल्यांकन किया जाता है।

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

सी # 4 में हम codegen बदल दिया है ताकि यह अब

bool entered = false; 
try { 
    System.Threading.Monitor.Enter(x, ref entered); 
    ... 
} 
finally { if (entered) System.Threading.Monitor.Exit(x); } 

लेकिन फिर से है, एक्स केवल एक बार का मूल्यांकन किया है। अपने कार्यक्रम में आप दो बार ताला अभिव्यक्ति मूल्यांकन कर रहे हैं। आपका कोड वास्तव में

bool lockTaken = false; 
    var temp = test2; 
    try { 
     System.Threading.Monitor.Enter(temp, ref lockTaken); 
     test2 = null; 
     Console.WriteLine("Manual collect 3."); 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     Console.WriteLine("Manual collect 4."); 
     GC.Collect(); 
    } 
    finally { 
     System.Threading.Monitor.Exit(temp); 
    } 

अब यह स्पष्ट है कि यह ऐसा क्यों करता है?

(भी ध्यान रखें कि सी # 4 में दर्ज अंदर कोशिश है, नहीं बाहर के रूप में यह सी # 3. में था)

+0

आपने 4.0 में प्रयास करने के अंदर इसे स्थानांतरित करने का निर्णय क्यों लिया? –

+11

@ ब्रायन: http://blogs.msdn.com/ericlippert/archive/2007/08/17/subtleties-of-c-il-codegen.aspx और फिर http://blogs.msdn.com/ericlippert/ पढ़ें संग्रह/2009/03/06/ताले और अपवाद--न-mix.aspx –

+0

हां, अब यह स्पष्ट है, और यह अपने आप को मेरा बुरा मैंने देखा नहीं था अंतर है। स्पष्टीकरण के लिए धन्यवाद। – Vokinneberg

संबंधित मुद्दे