2010-12-21 7 views
6

मैंने हाल ही में इस प्रश्न का उत्तर प्रदान किया: C# - Realtime console output redirectionएसिंक को कार्यान्वित करना "स्ट्रीम से वर्तमान में उपलब्ध सभी डेटा पढ़ें" ऑपरेशन

अक्सर होता है, सामानों को समझाते हुए (यहां "सामान" यह था कि मैंने एक जैसी समस्या का सामना कैसे किया) आपको अधिक समझ और/या, यहां मामला है, "ओह" क्षण। मुझे एहसास हुआ कि मेरा समाधान, लागू होने के रूप में, एक बग है। बग का थोड़ा व्यावहारिक महत्व नहीं है, लेकिन डेवलपर के रूप में मेरे लिए यह बहुत बड़ा महत्व है: मैं आसानी से यह जानना आराम नहीं कर सकता कि मेरे कोड में उड़ने की क्षमता है।

बग स्क्वैशिंग इस प्रश्न का उद्देश्य है। मैं लंबे परिचय के लिए क्षमा चाहता हूं, तो चलो गंदे हो जाएं।

मैं एक कक्षा बनाना चाहता था जो मुझे कंसोल के मानक आउटपुट Stream से इनपुट प्राप्त करने की अनुमति देता है। कंसोल आउटपुट स्ट्रीम FileStream प्रकार के हैं; अगर आवश्यक हो तो कार्यान्वयन उस पर डाल सकता है। लीवरेज के लिए पहले से जुड़े StreamReader भी मौजूद हैं।

मेरी वांछित कार्यक्षमता प्राप्त करने के लिए मुझे इस वर्ग में केवल एक चीज लागू करने की आवश्यकता है: एक async "इस पल उपलब्ध सभी डेटा को पढ़ें" ऑपरेशन। स्ट्रीम के अंत में पढ़ना व्यवहार्य नहीं है क्योंकि स्ट्रीम तब तक समाप्त नहीं होगी जब तक प्रक्रिया कंसोल आउटपुट हैंडल को बंद न करे, और ऐसा नहीं होगा क्योंकि यह इंटरैक्टिव है और जारी रखने से पहले इनपुट की अपेक्षा करता है।

मैं घटना-आधारित अधिसूचना को लागू करने के लिए उस hypothetical async ऑपरेशन का उपयोग करूँगा, जो मेरे कॉलर्स के लिए अधिक सुविधाजनक होगा।

वर्ग के सार्वजनिक इंटरफ़ेस यह है:

public class ConsoleAutomator { 
    public event EventHandler<ConsoleOutputReadEventArgs> StandardOutputRead; 

    public void StartSendingEvents(); 
    public void StopSendingEvents(); 
} 

StartSendingEvents और StopSendingEvents वे क्या विज्ञापित करते हैं; इस चर्चा के प्रयोजनों के लिए, हम मान सकते हैं कि घटनाओं को हमेशा सामान्यता के नुकसान के बिना भेजा जा रहा है।

वर्ग इन दो क्षेत्रों को आंतरिक रूप से उपयोग करता है:

protected readonly StringBuilder inputAccumulator = new StringBuilder(); 

    protected readonly byte[] buffer = new byte[256]; 

वर्ग की कार्यक्षमता नीचे दिए गए तरीकों में कार्यान्वित किया जाता। गेंद रोलिंग मिल के लिए:

public void StartSendingEvents(); 
    { 
     this.stopAutomation = false; 
     this.BeginReadAsync(); 
    } 

भी एक गाड़ी वापसी चार की आवश्यकता के बिना रोके बिना Stream से बाहर डेटा पढ़ने के लिए, और, BeginRead कहा जाता है:

protected void BeginReadAsync() 
    { 
     if (!this.stopAutomation) { 
      this.StandardOutput.BaseStream.BeginRead(
       this.buffer, 0, this.buffer.Length, this.ReadHappened, null); 
     } 
    } 

चुनौतीपूर्ण हिस्सा:

BeginRead को एक बफर का उपयोग करने की आवश्यकता है। इसका मतलब है कि स्ट्रीम से पढ़ने पर, यह संभव है कि पढ़ने के लिए उपलब्ध बाइट्स ("आने वाले खंड") बफर से बड़े होते हैं। याद रखें कि यहां लक्ष्य है जो सभी खंड पढ़ने के लिए है और प्रत्येक ग्राहक के लिए बिल्कुल एक बार ईवेंट ग्राहक पर कॉल करें।

इस अंत में, यदि बफर EndRead के बाद भरा हुआ है, तो हम इसकी सामग्री तुरंत ग्राहकों को नहीं भेजते हैं बल्कि इसके बजाय उन्हें StringBuilder पर जोड़ते हैं। StringBuilder की सामग्री केवल तब भी भेजी जाती है जब स्ट्रीम से पढ़ने के लिए और कुछ नहीं होता है।

private void ReadHappened(IAsyncResult asyncResult) 
    { 
     var bytesRead = this.StandardOutput.BaseStream.EndRead(asyncResult); 
     if (bytesRead == 0) { 
      this.OnAutomationStopped(); 
      return; 
     } 

     var input = this.StandardOutput.CurrentEncoding.GetString(
      this.buffer, 0, bytesRead); 
     this.inputAccumulator.Append(input); 

     if (bytesRead < this.buffer.Length) { 
      this.OnInputRead(); // only send back if we 're sure we got it all 
     } 

     this.BeginReadAsync(); // continue "looping" with BeginRead 
    } 

किसी भी पढ़ा जो बफर (इस स्थिति में हम जानते हैं कि कोई और अधिक डेटा पिछले पढ़ने आपरेशन के दौरान पढ़ा जाए थी) को भरने के लिए पर्याप्त नहीं है के बाद, सभी संचित डेटा ग्राहकों के लिए भेज दिया जाता है:

private void OnInputRead() 
    { 
     var handler = this.StandardOutputRead; 
     if (handler == null) { 
      return; 
     } 

     handler(this, 
       new ConsoleOutputReadEventArgs(this.inputAccumulator.ToString())); 
     this.inputAccumulator.Clear(); 
    } 

(मुझे पता है कि जब तक कोई ग्राहक नहीं होता है तब तक डेटा हमेशा जमा हो जाता है। यह एक जानबूझकर निर्णय है)।

अच्छा

यह योजना काम करता है लगभग पूरी तरह से: किसी भी धागे

  • बहुत बुला कोड के लिए सुविधाजनक को उत्पन्न करने के बिना

    • Async कार्यक्षमता (सिर्फ एक घटना की सदस्यता)
    • पढ़ने के लिए प्रत्येक बार डेटा उपलब्ध होने के लिए कभी भी एक से अधिक ईवेंट
    • बफर आकार

    करने के लिए लगभग नास्तिक है बुरा

    कि पिछले लगभग एक बहुत बड़ा एक है। गौर करें कि क्या होता है जब बफर के आकार के बराबर लंबाई के साथ आने वाला हिस्सा होता है। खंड को पढ़ा और buffered किया जाएगा, लेकिन घटना ट्रिगर नहीं किया जाएगा। इसके बाद BeginRead का पालन किया जाएगा जो वर्तमान खंड से संबंधित अधिक डेटा को एक टुकड़े में वापस भेजने के लिए अपेक्षा करता है, लेकिन ... स्ट्रीम में कोई और डेटा नहीं होगा।

    वास्तव में, जब तक डेटा को खंड में धारा में रखा जाता है, लंबाई के बराबर लंबाई के बराबर लंबाई के साथ, डेटा buffered किया जाएगा और घटना कभी ट्रिगर नहीं किया जाएगा।

    इस परिदृश्य में अभ्यास में होने की संभावना बहुत कम हो सकती है, खासकर जब से हम बफर आकार के लिए कोई भी नंबर चुन सकते हैं, लेकिन समस्या वहां है।

    समाधान?

    दुर्भाग्य से, FileStream और StreamReader पर उपलब्ध तरीकों की जाँच के बाद, मैं कुछ भी नहीं करते हुए भी इजाजत दी async तरीकों उस पर इस्तेमाल किया जा करने के लिए मुझे धारा में झांक सकते हैं जिसकी मदद से पा सकते हैं।

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

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

    चूंकि मुझे लगता है कि टाइमआउट के साथ एसिंक कॉल नंगे Win32 में एक विकल्प है, तो एक और दृष्टिकोण समस्या से बाहर नरक को पीएनवोक करना हो सकता है। लेकिन यह भी अवांछनीय है क्योंकि यह जटिलता पेश करेगा और कोड को दर्द होगा।

    क्या समस्या के आसपास आने का एक शानदार तरीका है?

    यह सब पढ़ने के लिए पर्याप्त रोगी होने के लिए धन्यवाद।

    अद्यतन:

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

    प्रश्न यह है कि "इस पल उपलब्ध सभी डेटा को पढ़ें" ऑपरेशन को एसिंक को कैसे कार्यान्वित किया जाए, इस बारे में सवाल है।

    उन लोगों के लिए मेरी माफ़ी, जिन्होंने मुझे अपना इरादा स्पष्ट किए बिना पढ़ने और जवाब देने का समय लिया।

  • +1

    मुझे विश्वास है कि धाराओं के बारे में आपकी धारणा गलत हो सकती है। "भाग" की कोई अवधारणा नहीं है। एक धारा बाइट्स का सिर्फ एक अनुक्रम है। प्रदत्त बफर को भरने के लिए रीड विधि की आवश्यकता नहीं है, इसे केवल कम से कम एक बाइट वापस करने की आवश्यकता है। उन हिस्सों को पुनर्निर्माण करने का कोई तरीका नहीं है जिसमें डेटा स्ट्रीम में लिखा गया था। – dtb

    +0

    @ डीटीबी: मुझे पता है कि सिद्धांत में धाराओं के पास "भाग" के साथ बिल्कुल कुछ नहीं है। अभ्यास में, हालांकि, आप उम्मीद करते हैं कि "खंड" को अक्सर स्ट्रीम के माध्यम से वितरित किया जाएगा (उदाहरण के लिए टीसीपी के माध्यम से संचार करने वाली दो प्रक्रियाएं निश्चित आकार के "संदेश" का आदान-प्रदान कर सकती हैं)। इस परिदृश्य में भी एक खंड की अवधारणा बहुत महत्वपूर्ण है, क्योंकि मैं कंसोल एप्लिकेशन को स्वचालित करना चाहता हूं। तो यहां, खंड जो भी स्वचालित प्रक्रिया स्ट्रीम फ्लश के बीच अपने मानक आउटपुट को लिखते हैं। प्रक्रिया फ्लश के साथ स्ट्रीम में "संरचना" प्रदान करती है; मैं उस रिवर्स-इंजीनियर को नहीं करना चाहता हूं। – Jon

    +1

    दाएं। लेकिन चूंकि किसी स्ट्रीम में भाग की कोई अवधारणा नहीं होती है, इसलिए स्ट्रीम पर संदेशों को भेजने वाले किसी भी एप्लिकेशन को इसके आकार को किसी तरह से संवाद करने की आवश्यकता होती है (उदाहरण के लिए, यह मानकर कि सभी संदेशों के समान, निश्चित आकार का आकार है; आकार के साथ संदेश को उपसर्ग करके; या संदेशों को किसी भी तरह से सीमित करके)। धाराओं में एक या कई हिस्सों में संदेशों को लिखा गया था या नहीं, इस बारे में जानकारी खो गई है। – dtb

    उत्तर

    1

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

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

    तो आखिरकार, आपको अपने पाठक को ज्ञान के साथ प्रमुखों को विभाजित करने की आवश्यकता होगी कि डेटा को खंडों में कैसे विभाजित किया जाना चाहिए, जिसका अर्थ है कि आपको क्लाइंट को डिकोडिंग करने की आवश्यकता है, यही कारण है कि बेस स्ट्रीम क्लासेस पहले से ही डेटा वितरित नहीं करते हैं जिस तरीके से आप कार्यान्वित करने की कोशिश कर रहे हैं।

    तो, इस मध्यवर्ती वर्ग को जोड़कर, आप क्या हासिल करेंगे? सबसे अच्छा यह आपके आई/ओ में जटिलता और ओवरहेड की एक अतिरिक्त परत जोड़ देगा (चलो इसका सामना करते हैं, आप अपने क्लाइंट कोड से बाहर निकलने का प्रयास कर रहे हैं केवल कोड की कुछ पंक्तियां हैं)। सबसे बुरी स्थिति में, यह आवश्यकतानुसार डेटा को तोड़ने में असमर्थ होगा, इसलिए इसका कोई उपयोग नहीं होगा।

    सावधान रहें "This scenario may be highly unlikely to occur in practice": बड़ी मात्रा में डेटा स्ट्रीम करते समय आपको आश्वस्त किया जा सकता है कि "नियमित रूप से" संभावनाएं नियमित नियमितता के साथ भी होती हैं - निश्चित रूप से अक्सर यह कहती है कि आप यह नहीं मान सकते कि वे कभी नहीं होंगे।

    [संपादित करें - जोड़ा]

    आप तो आप वर्ग है कि समस्या को आसानी से हैंडल करने के लिए तर्क जोड़ सकते हैं अपने समाधान सामान्यीकरण करने की मांग नहीं कर रहे हैं,।

    दो संभव समाधान हैं:

    • आप कंसोल लाइनों है कि आप करने के लिए उत्पादन किया जाएगा की अधिकतम सीमा पता है, तो आप बस एक बड़ा पर्याप्त बफर है कि आप अपने बढ़त मामले गारंटी ले सकते हैं का उपयोग कर सकते कभी नहीं होगा पाए जाते हैं। (उदाहरण के लिए CreateProcess आदेश 32k तक सीमित हैं, cmd.exe सीमाएं 8k तक सीमित हैं। आपको प्राप्त होने वाले डेटा "भाग" पर समान सीमाएं मिल सकती हैं)

    • यदि आपके भाग हमेशा रेखाएं हैं (न्यूलाइन टेक्स्ट के ब्लॉक को समाप्त कर दिया गया है), फिर बस जांचें कि क्या आपके बफर में अंतिम वर्ण टर्मिनेटर (0x0a या 0x0d) जैसा दिखता है। यदि नहीं, तो पढ़ने के लिए और डेटा है।

    +0

    आपका सामान्य बिंदु सही है। हालांकि, यह एक वर्ग विशेष रूप से कंसोल के आउटपुट से पढ़ने के लिए लक्षित है। एक फ़ाइल नहीं, नेटवर्क स्ट्रीम नहीं। आपका उत्तर बताता है कि मैंने संभवतः प्रश्न को अधिक सामान्यीकृत किया है, लेकिन दुर्भाग्यवश सवाल का जवाब नहीं देता है। – Jon

    +0

    @ जोन: मेरे उत्तरों के नीचे मेरे संपादन देखें। –

    +0

    32K/8K कमांड लाइन लंबाई की सीमा है, आउटपुट की लंबाई नहीं, है ना? – Jon

    2

    सिद्धांत रूप में, मैं जेसन से सहमत हूं; आपके कार्यान्वयन में आपके बफर द्वारा समान रूप से विभाजित होने वाले डेटा के एक हिस्से के मामले में तर्क छेद होने की तुलना में बड़ी समस्याएं हैं। सबसे बड़ी समस्या यह है कि आपके पाठक को फ़ाइल प्रकार के बारे में पर्याप्त जानकारी होनी चाहिए ताकि यह पता चल सके कि यह डेटा को "भाग" में कैसे अलग कर सकता है, जिससे आपके ग्राहकों को पता चल सके कि कैसे निपटें।

    स्ट्रीम के पास प्राप्त करने या भेजने के बारे में कोई अंतर्निहित ज्ञान नहीं है; केवल वह तंत्र जिसके द्वारा वे डेटा ले जा रहे हैं। एक नेटवर्कस्ट्रीम HTML या ज़िप फ़ाइल भेज रहा है; एक फ़ाइलस्ट्रीम एक पाठ फ़ाइल या एमपी 3 पढ़ रहा हो सकता है। यह पाठक है (XmlReader, TextReader, Image.FromStream(), आदि) जिसमें यह ज्ञान है। इसलिए, आपके एसिंक पाठक को डेटा के बारे में कम से कम कुछ पता होना चाहिए, लेकिन यह उपयोगी होगा कि वह ज्ञान कड़ी-कोडित न हो।

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

    एक तार्किक कार्यान्वयन:

    public class MyStreamManager { 
        public delegate bool ValidChunkTester(StringBuilder builder); 
    
        private readonly List<ValidChunkTester> validators = new List<ValidChunkTester>(); 
        public event ValidChunkTester IsValidChunk 
        { add{validators.Add(value);} remove {validators.Remove(value);}} 
    
        public event EventHandler<ConsoleOutputReadEventArgs> StandardOutputRead; 
    
    
        public void StartSendingEvents(); 
        public void StopSendingEvents(); 
    } 
    
    ... 
    
    private void ReadHappened(IAsyncResult asyncResult) 
    { 
        var bytesRead = this.StandardOutput.BaseStream.EndRead(asyncResult); 
        if (bytesRead == 0) { 
         this.OnAutomationStopped(); 
         return; 
        } 
    
        var input = this.StandardOutput.CurrentEncoding.GetString(
         this.buffer, 0, bytesRead); 
        this.inputAccumulator.Append(input); 
    
        if (validators.Any() && StandardOutputRead !-= null 
          && validators.Aggregate(true, (valid, validator)=>valid && validator(inputAccumulator))) { 
         this.OnInputRead(); // send when all listeners can work with the buffer contents 
        } 
    
        this.BeginReadAsync(); // continue "looping" with BeginRead 
    } 
    
    ... 
    

    यह मॉडल है कि ग्राहकों StringBuilder संशोधित नहीं की आवश्यकता है, यदि आप चुनते हैं तो आप जांचने के लिए कुछ अपरिवर्तनीय प्रदान कर सकते हैं। एक उदाहरण श्रोता हो सकता है:

    public bool IsACompleteLine(StringBuilder builder) 
    { 
        return builder.Contains(Environment.NewLine); 
    } 
    

    या:

    public bool Contains256Bytes(StringBuilder builder) 
    { 
        return builder.Length >= 256; 
    } 
    

    ... आप विचार मिलता है। श्रोताओं को जारी किए जाने वाले वर्तमान बफर की योग्यता का निर्धारण करने वाला कार्यक्रम अवधारणाओं से खुद को अलग-अलग है, लेकिन इसे ठोस रूप से नहीं होना चाहिए, इसलिए यह एक आउटपुट-विशिष्ट परीक्षण या एकाधिक श्रोता-आधारित परीक्षणों का समर्थन करेगा।

    +0

    +1। हालांकि, मुझे लगता है कि मैंने बस अपने इरादे को पर्याप्त रूप से संवाद नहीं किया। यदि आपके पास अवसर है तो कृपया संशोधित प्रश्न पढ़ें। – Jon

    0

    मैं "डबल बफरिंग" (भाग जहां आप स्ट्रिंगबिल्डर भरते हैं, फिर डेटा भरने के बाद डेटा को पास करते हैं) को हटाने और बाइट पढ़ने के दौरान स्ट्रीम के बफर से प्राप्त डेटा को वापस करने के इच्छुक होंगे।ReadHappened में तो, आप होगा: दूसरों

    if (bytesRead > 0) { 
        this.OnInputRead(); // only send back if we 're sure we got it all 
    } 
    

    के रूप में कहा है ग्राहक डेटा का संदेश/हिस्सा और कैसे एक पूरे में कई भागों गठबंधन करने के लिए के बारे में कुछ जानने की आवश्यकता होगी। इसलिए आप इसे प्राप्त करने के रूप में प्रत्येक भाग को भी वापस कर सकते हैं। यदि ग्राहक एक "गूंगा ग्राहक" है जो बस एक कंडिशन के रूप में कार्य करता है तो यह भी काम करेगा।

    +0

    मैंने सवाल संशोधित किया, क्योंकि यह स्पष्ट रूप से भ्रामक था। अंत में अद्यतन देखें। मुझे लालची होने की जरूरत है और एक ही समय में पढ़ने के लिए उपलब्ध सभी डेटा लौटाएं। समस्या यह है कि यह कैसे बताना है कि मैंने इसे सब कुछ पढ़ा है और थोड़ी देर के लिए और कुछ नहीं होगा। – Jon

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