2010-10-13 9 views
5

मैं समझता हूं कि यह एक कार्यान्वयन विस्तार है। मैं वास्तव में उत्सुक हूं कि माइक्रोसॉफ्ट के सीएलआर में है।माइक्रोसॉफ्ट के सीएलआर में एसिंक्रोनस विधि कॉल के लिए संग्रहीत रेफ वैल्यू टाइप पैरामीटर कहां हैं?

अब, मेरे साथ सहन मैं कॉलेज में सीएस का अध्ययन के रूप में नहीं था, इसलिए मैं कुछ मौलिक सिद्धांतों से चूक गए हो सकता है।

लेकिन "ढेर" और "ढेर" CLR में लागू के रूप में यह आज खड़ा है की मेरी समझ, ठोस है मुझे लगता है,। मैं कुछ गलत छतरी बयान नहीं दे रहा हूं जैसे कि "मूल्य प्रकार स्टैक पर संग्रहीत हैं," उदाहरण के लिए। लेकिन, सबसे आम परिदृश्यों में - सादे वेनिला स्थानीय चर, मान प्रकार के, या तो पैरामीटर के रूप में पारित कर दिया या घोषित विधि के भीतर और एक बंद अंदर निहित नहीं - मान प्रकार चर फिर ढेर (पर संग्रहीत, माइक्रोसॉफ्ट के CLR में हैं)।

मुझे लगता है कि मैं क्या है की अनिश्चित हूँ जहां ref मूल्य प्रकार पैरामीटर में आते हैं

मूलतः क्या मैं सोच रहा था कि, कॉल स्टैक इस (बाएं = नीचे) की तरह लग रहा है, तो:।

A() -> B() -> C() 

... तो एक स्थानीय चर एक के दायरे के भीतर घोषित कर दिया और एक ref पैरामीटर करने के लिए बी अभी भी ढेर पर संग्रहीत किया जा सकता है के रूप में पारित - यह couldn't? बी बस स्मृति स्थान जहां कि स्थानीय चर एक की सीमा के भीतर जमा हो गया था की आवश्यकता होगी (मुझे माफ कर दो, तो यह सही शब्दावली नहीं है, मुझे लगता है कि यह स्पष्ट है कि मैं क्या मतलब है, वैसे भी)।

मैं, महसूस किया कि यह पूर्णतया सत्य नहीं हो सकता है हालांकि, जब यह मेरे लिए हुआ मैं यह कर सकता है कि:

delegate void RefAction<T>(ref T arg); 

void A() 
{ 
    int x = 100; 

    RefAction<int> b = B; 

    // This is a non-blocking call; A will return immediately 
    // after this. 
    b.BeginInvoke(ref x, C, null); 
} 

void B(ref int arg) 
{ 
    // Putting a sleep here to ensure that A has exited by the time 
    // the next line gets executed. 
    Thread.Sleep(1000); 

    // Where is arg stored right now? The "x" variable 
    // from the "A" method should be out of scope... but its value 
    // must somehow be known here for this code to make any sense. 
    arg += 1; 
} 

void C(IAsyncResult result) 
{ 
    var asyncResult = (AsyncResult)result; 
    var action = (RefAction<int>)asyncResult.AsyncDelegate; 

    int output = 0; 

    // This variable originally came from A... but then 
    // A returned, it got updated by B, and now it's still here. 
    action.EndInvoke(ref output, result); 

    // ...and this prints "101" as expected (?). 
    Console.WriteLine(output); 
} 

इसलिए उपरोक्त उदाहरण में, जहां x (में एक की है गुंजाइश) संग्रहित? और यह कैसे काम करता है? क्या यह बॉक्स किया गया है? यदि नहीं, तो क्या यह मूल्य प्रकार होने के बावजूद अब कचरा संग्रहण के अधीन है? या फिर स्मृति को तुरंत पुनः प्राप्त किया जा सकता है?

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

+0

http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx – Brian

उत्तर

4

मैं नहीं मानता कि जब आप ref या out तर्क के साथ BeginInvoke() और EndInvoke() का उपयोग आप सही मायने में रेफरी द्वारा चर गुजर रहे हैं। तथ्य यह है कि हमें EndInvoke() पर ref पैरामीटर के साथ कॉल करना होगा, साथ ही इसके लिए एक सुराग होना चाहिए।

के व्यवहार मैं वर्णन प्रदर्शित करने के लिए अपने उदाहरण बदल डालते हैं:

void A() 
{ 
    int x = 100; 
    int z = 400; 

    RefAction<int> b = B; 

    //b.BeginInvoke(ref x, C, null); 
    var ar = b.BeginInvoke(ref x, null, null); 
    b.EndInvoke(ref z, ar); 

    Console.WriteLine(x); // outputs '100' 
    Console.WriteLine(z); // outputs '101' 
} 

अब आप आउटपुट का परीक्षण, तो आप उस x का मूल्य वास्तव में कोई बदलाव नहीं है देखेंगे। लेकिन z में अब अद्यतन मान शामिल है।

मुझे लगता है कि संकलक ref द्वारा चर गुजर के शब्दों को बदल देता है तो आप एसिंक्रोनस शुरू/EndInvoke तरीकों का उपयोग।

इस कोड द्वारा उत्पादित आईएल पर एक नज़र लेने के बाद, ऐसा लगता है कि BeginInvoke() करने के लिए ref तर्क अभी भी पारित कर रहे हैं by ref। जबकि रिफ्लेक्टर इस विधि के लिए आईएल नहीं दिखाता है, मुझे संदेह है कि यह पैरामीटर के साथ ref तर्क के रूप में पास नहीं होता है, बल्कि इसके बजाय B() पर जाने के लिए दृश्यों के पीछे एक अलग चर बनाता है। जब आप EndInvoke() पर कॉल करते हैं तो आपको एसिंक स्थिति से मूल्य पुनर्प्राप्त करने के लिए फिर से ref तर्क प्रदान करना होगा। ऐसा लगता है कि इस तरह के तर्क वास्तव में IAsyncResult ऑब्जेक्ट के हिस्से (या साथ में) के हिस्से के रूप में संग्रहीत किए जाते हैं, जो अंततः उनके मूल्यों को पुनः प्राप्त करने के लिए आवश्यक होते हैं।

चलो इस बारे में सोचें कि व्यवहार संभवतः इस तरह क्यों काम करता है। जब आप किसी विधि को एसिंक कॉल करते हैं, तो आप एक अलग थ्रेड पर ऐसा कर रहे हैं।इस धागे का अपना ढेर है और इसलिए ref/out चर के एलिसिंग के सामान्य तंत्र का उपयोग नहीं कर सकता है। हालांकि, किसी एसिंक विधि से किसी भी लौटाए गए मान प्राप्त करने के लिए, आपको ऑपरेशन को पूरा करने और इन मानों को पुनर्प्राप्त करने के लिए अंततः EndInvoke() पर कॉल करने की आवश्यकता है। हालांकि, EndInvoke() पर कॉल BeginInvoke() या विधि के वास्तविक निकाय के मूल कॉल की तुलना में पूरी तरह से अलग थ्रेड पर आसानी से हो सकता है। स्पष्ट रूप से कॉल स्टैक ऐसे डेटा को स्टोर करने के लिए एक अच्छी जगह नहीं है - खासकर जब एसिंक कॉल के लिए उपयोग किए गए थ्रेड को एसिंक ऑपरेशन पूरा होने के बाद एक अलग विधि के लिए फिर से तैयार किया जा सकता है। नतीजतन, स्टैक के अलावा कुछ तंत्र को "मार्शल" वापसी मूल्य और आउट/रेफ तर्कों को उस साइट पर वापस बुलाया जा रहा है जहां उन्हें अंततः उपयोग किया जाएगा।

मुझे विश्वास है कि यह तंत्र (माइक्रोसॉफ्ट .NET कार्यान्वयन में) IAsyncResult ऑब्जेक्ट है। वास्तव में, यदि आप डीबगर में IAsyncResult ऑब्जेक्ट की जांच करते हैं, तो आप देखेंगे कि गैर-सार्वजनिक सदस्यों में _replyMsg मौजूद है, जिसमें Properties संग्रह शामिल है। इस संग्रह में __OutArgs और __Return जैसे तत्व शामिल हैं जिनका डेटा उनके नामों को प्रतिबिंबित करने के लिए दिखाई देता है।

संपादित करें:यहाँ async प्रतिनिधि डिजाइन के बारे में एक सिद्धांत यह है कि मेरे पास होता है। ऐसा लगता है कि BeginInvoke() और EndInvoke() के हस्ताक्षर भ्रम से बचने और स्पष्टता में सुधार के लिए एक-दूसरे के समान संभव होने के लिए चुने गए थे। BeginInvoke() विधि वास्तव में ref/out तर्क स्वीकार करने के लिए आवश्यक नहीं है - क्योंकि इसे केवल उनके मूल्य की आवश्यकता है ... उनकी पहचान नहीं (क्योंकि यह कभी भी उन्हें वापस सौंपने वाला नहीं है)। हालांकि यह वास्तव में अजीब होगा (उदाहरण के लिए) BeginInvoke() कॉल करने के लिए जो int और EndInvoke() कॉल लेता है जो ref int लेता है। अब, यह संभव है कि तकनीकी कारण हैं कि क्यों प्रारंभ/समाप्ति कॉल में समान हस्ताक्षर होना चाहिए - लेकिन मुझे लगता है कि स्पष्टता और समरूपता के लाभ ऐसे डिज़ाइन को प्रमाणित करने के लिए पर्याप्त हैं।

यह सब, निश्चित रूप से, सीएलआर और सी # कंपाइलर का कार्यान्वयन विवरण है और भविष्य में बदल सकता है। हालांकि, यह दिलचस्प है कि भ्रम की संभावना है - यदि आप उम्मीद करते हैं कि मूल चर BeginInvoke() पर वास्तव में संशोधित किया जाएगा। यह एसिंक ऑपरेशन को पूरा करने के लिए EndInvoke() पर कॉल करने के महत्व को भी रेखांकित करता है।

शायद सी # टीम (यदि वे इस प्रश्न को देखते हैं) से कोई व्यक्ति इस कार्यक्षमता के पीछे विवरण और डिज़ाइन विकल्पों में अधिक अंतर्दृष्टि प्रदान कर सकता है।

+0

वाह, महान परीक्षण। मैंने कोशिश करने के लिए भी सोचा नहीं था (वास्तव में, मुझे लगता है कि मैंने * माना * कि अगर मैंने ** ए ** के फ्रेम के भीतर से 'एंडइवोक' कहा है, तो यह मेरे निष्कर्षों को अमान्य कर देगा क्योंकि पूरे मुद्दे के बारे में मुझे अनिश्चित था एक बार 'रेफरी पैरामीटर एक बार संग्रहीत किया जाता है ** ** ** फ्रेम अब उपलब्ध नहीं है)! यह मजाकिया है, यद्यपि; ऐसा लगता है कि भ्रम के एक बिंदु को साफ़ करना प्रतीत होता है ('रेफ' पैरामीटर स्पष्ट रूप से मूल चर के स्थान को इंगित नहीं करता है) दूसरे के बदले में (इसलिए' BeginInvoke 'को पारित' रेफरी 'पैरामीटर वास्तव में' ref 'नहीं है 'पैरामीटर बिल्कुल?)। –

+0

@ दैन ताओ: जैसा कि मैंने उपरोक्त उल्लेख किया है, परावर्तक से आईएल इंगित करता है कि 'BeginInvoke()' के लिए 'ref' तर्क वास्तव में रेफरी द्वारा पारित किए जाते हैं। मुझे संदेह है कि, आंतरिक रूप से 'BeginInvoke()' मूल्य को 'IAsyncResult' ऑब्जेक्ट में प्रतिलिपि बनाता है और प्रतिलिपि '' '(' '' से प्रतिलिपि पास करता है। आखिरकार, केवल 'ए()' असंगतता का निरीक्षण कर सकता है, अगर यह 'EndInvoke()' को कॉल करते समय 'x' के अलावा किसी चर को पारित करना चुनता है। – LBushkin

+0

हां, जैसा कि हंस ने अपने अद्यतन उत्तर में उल्लेख किया है (यदि मैं उसे सही ढंग से समझता हूं), 'BeginInvoke' कॉल को' रेफरी पैरामीटर 'दिया जाता है जो मूल चर के * प्रति * के स्थान पर इंगित करता है। मैंने इसे एक गैर-स्थानीय चर के साथ भी परीक्षण किया - वास्तव में - एक उदाहरण क्षेत्र - और एक ही व्यवहार देखा (इसलिए यह केवल व्यवहार नहीं है जो केवल इस प्रदूषित उदाहरण में महत्वपूर्ण है): फ़ील्ड को 'रेफरी' 'BeginInvoke' कॉल के पैरामीटर ने वास्तव में फ़ील्ड के मान को नहीं बदला है। –

2

पता लगाने के लिए परावर्तक के साथ उत्पन्न कोड को देखो। मेरा अनुमान है कि x युक्त अज्ञात वर्ग उत्पन्न होता है, जैसे कि जब आप बंद का उपयोग करते हैं (लैम्ब्डा अभिव्यक्ति जो वर्तमान स्टैक फ्रेम में संदर्भ चर)। इसके बारे में भूल जाओ और अन्य उत्तरों को पढ़ें।

+0

यह मामला प्रतीत नहीं होता है। अधिक जानकारी के लिए मेरा जवाब देखें। – LBushkin

+0

@ एल बुशकिन: आआआआआ। तो मेरा अनुमान गलत है। –

3

CLR पूरी तरह से इस पर लूप से बाहर है, यह JIT कम्पाइलर एक तर्क संदर्भ द्वारा पारित करने हेतु उचित मशीन कोड उत्पन्न करने के लिए का काम है। जो स्वयं में एक कार्यान्वयन विस्तार है, विभिन्न मशीन आर्किटेक्चर के लिए अलग-अलग झटके हैं।

लेकिन आम लोगों यह वास्तव में जिस तरह से एक सी प्रोग्रामर यह करता है, वे चर के लिए सूचक गुजरती हैं। वह पॉइंटर सीपीयू रजिस्टर या स्टैक फ्रेम पर पारित होता है, इस पर निर्भर करता है कि विधि कितनी तर्क लेती है।

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

आपका कोड स्निपेट .NET फ्रेमवर्क के अंदर जादू को आमंत्रित करता है जिसे एक धागे से दूसरे काम में मार्शलिंग कॉल करने की आवश्यकता होती है। यह एक ही तरह की नलसाजी है जो रिमोटिंग काम करता है। ऐसी कॉल करने के लिए, थ्रेड पर एक नया स्टैक फ्रेम बनाया जाना चाहिए जहां कॉल किया जाता है। रिमोटिंग कोड प्रतिनिधि के प्रकार की परिभाषा का उपयोग यह जानने के लिए करता है कि उस स्टैक फ्रेम की तरह दिखना चाहिए। और यह संदर्भ द्वारा पारित तर्कों से निपट सकता है, यह जानता है कि इसे आपके मामले में पॉइंट-टू वेरिएबल, i स्टोर करने के लिए स्टैक फ्रेम में स्लॉट आवंटित करने की आवश्यकता है। BeginInvoke कॉल रीमोटेड स्टैक फ्रेम में i चर की प्रति प्रारंभ करता है।

वही बात EndInvoke() कॉल पर होती है, परिणाम थ्रेडपूल थ्रेड में स्टैक फ्रेम से वापस कॉपी किए जाते हैं। मुख्य बिंदु यह है कि वास्तव में i चर के लिए कोई सूचक नहीं है, इसकी प्रतिलिपि के लिए एक सूचक है।

इतना यकीन नहीं है कि यह उत्तर बहुत स्पष्ट है, सीपीयू कैसे काम करता है और थोड़ा सी ज्ञान के बारे में कुछ समझ है, इसलिए पॉइंटर की अवधारणा क्रिस्टल बहुत मदद कर सकती है।

+1

मुझे लगता है कि ओपीएस उदाहरण का निर्माण किया गया है ताकि 'ए()' का ढेर फ्रेम अब उपलब्ध न हो। इसलिए सवाल यह है कि कैसे वैरिएबल एसिंक्रोनस विधि को रेफरी द्वारा पारित किया जाता है। – LBushkin

+0

धन्यवाद @ एल बुशकिन, मुझे याद आया। पोस्ट अपडेट किया गया। –

+0

क्या जिटर को आम भाषा रनटाइम का हिस्सा नहीं माना जाता है (जो स्वयं सीएलआई वर्चुअल एक्जिक्यूशन सिस्टम का कार्यान्वयन है, एक्मा -335 §12 देखें)? – Frank