2012-12-10 33 views
27

मुझे await कीवर्ड काम करता है, और मैं इसकी समझ को थोड़ा सा विस्तारित करना चाहता हूं, इसकी एक नाजुक समझ है।रिकर्सन और प्रतीक्षा/एसिंक कीवर्ड

जो मुद्दा अभी भी मेरे सिर स्पिन बनाता है वह रिकर्सन का उपयोग है। यहाँ एक उदाहरण है:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace TestingAwaitOverflow 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var task = TestAsync(0); 
      System.Threading.Thread.Sleep(100000); 
     } 

     static async Task TestAsync(int count) 
     { 
      Console.WriteLine(count); 
      await TestAsync(count + 1); 
     } 
    } 
} 

यह एक स्पष्ट रूप से एक StackOverflowException फेंकता है।

मेरी समझ इसलिए है क्योंकि कोड वास्तव में पहली एसिंक्रोनस एक्शन तक सिंक्रनाइज़ेशन चलाता है, जिसके बाद यह Task ऑब्जेक्ट देता है जिसमें एसिंक्रोनस ऑपरेशन के बारे में जानकारी होती है। इस मामले में, कोई एसिंक्रोनस ऑपरेशन नहीं है, इस प्रकार यह झूठे वादे के तहत अभी भी पुनरावर्ती रहता है कि इसे अंततः Task वापस कर दिया जाएगा।

अब यह बदल रहा है सिर्फ एक छोटा सा:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace TestingAwaitOverflow 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var task = TestAsync(0); 
      System.Threading.Thread.Sleep(100000); 
     } 

     static async Task TestAsync(int count) 
     { 
      await Task.Run(() => Console.WriteLine(count)); 
      await TestAsync(count + 1); 
     } 
    } 
} 

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

तो मैं दो प्रश्न हैं:

  • कैसे कोड के दूसरे बैच एक StackOverflowException से बचने करता है?
  • क्या कोड का दूसरा बैच अन्य संसाधनों को बर्बाद करता है? (उदाहरण के लिए यह ढेर पर एक बड़ी संख्या में कार्य वस्तुओं को आवंटित करता है?)

धन्यवाद!

उत्तर

16

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

पहला इंतजार (जो तुरंत पूरा नहीं होता है - यह आपके लिए उच्च संभावना वाले मामले में है) फ़ंक्शन को वापस करने का कारण बनता है (और इसकी स्टैक स्पेस छोड़ने के लिए!)। यह बाकी के रूप में एक निरंतरता के रूप में queues। टीपीएल सुनिश्चित करता है कि निरंतरता कभी भी गहराई से घोंसला न करें। यदि स्टैक ओवरफ्लो का जोखिम होता है तो थ्रेड पूल में निरंतरता कतारबद्ध होती है, जो स्टैक को रीसेट कर रही है (जो भरना शुरू हो रहा था)।

दूसरा उदाहरण अभी भी बह रहा है! क्या होगा अगर Task.Run कार्य हमेशा तुरंत पूरा हो गया? (यह सही ओएस थ्रेड शेड्यूलिंग के साथ असंभव है लेकिन संभव है)। फिर, एसिंक फ़ंक्शन कभी भी बाधित नहीं होगा (इसे वापस करने और सभी स्टैक स्पेस को मुक्त करने के कारण) और उसी व्यवहार के मामले में 1 परिणाम होगा।

+0

तो यदि मैंने एक कार्य के साथ 'कार्य.रुन()' को प्रतिस्थापित किया है जो तुरंत एक पूर्ण 'कार्य' ऑब्जेक्ट लौटाता है, तो यह स्टैक ओवरफ़्लो अपवाद को पुन: पेश करेगा? (या मैंने बस ऐसा कुछ प्रस्तावित किया जो संभव नहीं है?) – riwalk

+0

@ Stargazer712 न केवल यह संभव है, यह वास्तव में वही करेगा जो आप वर्णन कर रहे हैं। बस 'टास्क का प्रयास करें। FromResult (शून्य);' यही कारण है कि आपको एक छोटी अवधि में 'प्रतीक्षा' करने के लिए एक ही फ़ंक्शन से बचना चाहिए; आपको यह सुनिश्चित करना चाहिए कि प्रतीक्षा किए गए कार्य ऑपरेशन में काफी लंबे समय तक हैं, या उनमें से एक छोटी सी सीमित संख्या है जैसे कि उन्हें सिंक्रनाइज़ करना ठीक है। – Servy

+5

@ Stargazer712 हां! और यदि आपने इसे 'टास्क.इल्ड() '(जिसे निरंतरता पोस्ट करने की आवश्यकता है) के साथ प्रतिस्थापित किया है तो आपको स्टैक ओवरफ़्लो (प्रदर्शन लागत पर) से गारंटी प्राप्त स्वतंत्रता प्राप्त होगी। ध्यान दें, कि 'यील्ड' 'कार्य 'के अलावा एक प्रतीक्षा योग्य लौटाता है। यह गारंटी किसी कार्य से प्राप्त करना बहुत कठिन है क्योंकि आपको यह सुनिश्चित करना होगा कि प्रतीक्षा सुविधा द्वारा पूछे जाने पर यह पूरा नहीं हुआ है। – usr

0

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

संपादित करें: मैंने थ्रेड या थ्रेड स्टोर करना जारी रखा और मेरा दिनचर्या था। वे सभी एक ही धागे पर हैं जो आपके उदाहरण में मुख्य धागा है। क्षमा करें अगर मैंने आपको उलझन में डाल दिया।

+1

लेकिन यहां कोई ढेर नहीं होता है, केवल ढेर आवंटित कार्य की श्रृंखला, जो प्रत्येक-दूसरे पर इंगित करती है, और हमेशा के लिए प्रतीक्षा करती है। –

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