जब हम async EventHandlers पर चर्चा करते हैं, तो पहली बात जो हममें से कई लोगों के दिमाग में आती है, वह यह है कि यह एकमात्र अपवाद है जिसकी हम अनुमति देते हैं
जब मैंने इसके बारे में पहले लिखा था, तो मैं उत्साहित था कि मैं एक ऐसे समाधान की खोज कर रहा था जो वास्तव में एसिंक्स शून्य को अस्तित्व में लाने की इजाजत दे रहा था (बाकी बालों को खींचने के बिना)।
मेरे लिए, यह कुछ चतुर तरकीबों के बारे में अधिक था जिसका उपयोग हम एसिंक्स इवेंटहैंडलर को दूर करने के लिए कर सकते हैं, क्योंकि यह पूरी तरह से समस्या से बचने के लिए समाधान प्रदान करने के लिए था।
इसके साथ ही, लेख पर बहुत अधिक कर्षण था, जिसके लिए मैं बहुत आभारी हूं, और कुछ लोगों ने राय व्यक्त की कि वे async EventHandlers को एक अलग तरीके से हल करेंगे।
मैंने सोचा था कि यह एक महान बिंदु था, इसलिए मैं एक वैकल्पिक दृष्टिकोण के साथ आना चाहता था जो async शून्य को ठीक नहीं करता है, लेकिन यह आपको कुछ चुनौतियों को हल करते हुए पूरी तरह से इसे शून्य करने की अनुमति देता है (देखें कि मैंने वहां क्या किया?) async EventHandlers के साथ।
इस लेख में, मैं एक और समाधान प्रस्तुत करूंगा जिसे आप अपने कोड में आजमा सकते हैं। हम अपने दृष्टिकोण से पेशेवरों और विपक्षों को संबोधित करेंगे कि इसका उपयोग कैसे किया जा सकता है ताकि आप यह तय कर सकें कि यह आपके उपयोग के मामले में समझ में आता है या नहीं।
आप .NET फिडल पर कुछ इंटरएक्टिव कोड भी पा सकते हैं
एक साथी वीडियो!
समस्या
Async EventHandlers के साथ हमें जो समस्या है, वह यह है कि उन घटनाओं के लिए हस्ताक्षर जिन्हें हम डिफ़ॉल्ट रूप से C# में सब्सक्राइब कर सकते हैं, कुछ इस तरह दिखते हैं:
void TheObject_TheEvent(object sender, EventArgs e);
और, आप देखेंगे कि इस सिग्नेचर के सामने वाले भाग को वॉइस आउट करके, हम ईवेंट को सब्सक्राइब करने के लिए अपने स्वयं के हैंडलर्स में वॉइस का उपयोग करने के लिए मजबूर हैं।
इसका मतलब यह है कि यदि आप चाहते हैं कि आपका हैंडलर कभी भी async/प्रतीक्षा कोड चलाए, तो आपको अपने void मेथड के अंदर प्रतीक्षा करने की आवश्यकता होगी ... जो कि बड़े डरावने async void पैटर्न का परिचय देता है जिससे हमें प्लेग की तरह बचने के लिए कहा जाता है।
और क्यों? क्योंकि async शून्य अपवादों को ठीक से बुलबुला करने की क्षमता को तोड़ देता है और परिणामस्वरूप सिरदर्द का एक टन पैदा कर सकता है।
- हमें इसके लिए उन वस्तुओं पर समर्थन की आवश्यकता हो सकती है जिनके लिए हम घटनाओं के आह्वान को नियंत्रित नहीं करते हैं (यानी, आप अपने पसंदीदा यूआई ढांचे में एक बटन के क्लिक इवेंट को जोड़ रहे हैं)
- कुछ लोग उस समाधान के अंदर संदर्भ के उपयोग को हैक के रूप में देखते हैं (मैं इससे असहमत नहीं हूं)।
- ... विशेष रूप से, ईवेंट हैंडलर्स के साथ, हमारे पास कुछ और सरल तरकीबें हैं जो हम एसिंक्स इवेंटहैंडलर्स का समर्थन करने के लिए कर सकते हैं!
मेरी राय में, सरल बेहतर है ... इसलिए यदि आपने मेरा पिछला लेख async शून्य पर पढ़ा है और आपका लक्ष्य वास्तव में केवल EventHandlers से निपटना था, तो इससे मदद मिलनी चाहिए।
कोशिश/पकड़ के साथ Async EventHandlers को हल करना
पहले बताई गई शर्तों के आधार पर, एसिंक्स शून्य की सीमा पर अपवाद हैंडलिंग टूट जाती है। यदि आपके पास एक अपवाद है जिसे इस सीमा को पार करने की जरूरत है, तो आप एक मजेदार समय के लिए जा रहे हैं।
और मज़े से, मेरा मतलब है कि अगर आप डिबगिंग का आनंद लेते हैं कि सामान क्यों काम नहीं कर रहा है और आपके पास स्पष्ट संकेत नहीं है कि क्या टूट रहा है, तो आपके पास वास्तव में बहुत अच्छा समय होगा।
तो इसे ठीक करने का सबसे आसान तरीका क्या है?
आइए अपवादों को इस सीमा को पार करने में सक्षम होने से पहले एक साधारण टूल का उपयोग करके रोकें, जिसकी हमारे पास पहुंच है: कोशिश/पकड़ें।
objectThatRaisesEvent.TheEvent += async (s, e) => { // if the try catch surrounds EVERYTHING in the handler, no exception can bubble up try { await SomeTaskYouWantToAwait(); } catch (Exception ex) { // TODO: put your exception handling stuff here } // no exception can escape here if the try/catch surrounds the entire handler body }
जैसा कि ऊपर दिए गए कोड में उल्लेख किया गया है, यदि आप अपने ईवेंट हैंडलर के ENTIRE बॉडी के चारों ओर एक कोशिश/कैच ब्लॉक लगाते हैं, तो आप किसी भी अपवाद को उस async शून्य सीमा के ऊपर बुदबुदाने से रोक सकते हैं। सतह पर, यह काफी सरल है और इसे लागू करने के लिए किसी भी कल्पना की आवश्यकता नहीं है।
पेशेवरों:
- अत्यंत सरल। समझने के लिए कोई जटिल तंत्र नहीं।
- किसी पैकेज की आवश्यकता नहीं है।
- इसके काम करने के लिए आपको उस वर्ग का स्वामी होने की आवश्यकता नहीं है जो ईवेंट को बढ़ाता है। इसका मतलब यह है कि यह दृष्टिकोण WinForms और WPF UI घटकों सहित सभी मौजूदा ईवेंट-राइजिंग ऑब्जेक्ट्स के लिए काम करेगा।
दोष:
- ऐसा करने के लिए आपको याद रखने की जरूरत है... हर जगह।
- यह संभव है कि जैसे-जैसे आपका कोड समय के साथ विकसित होता है, कोई गलती से इवेंट हैंडलर के ट्राइ-कैच के बाहर तर्क लिख सकता है जो अपवादों को फेंक सकता है
उस के साथ, यह समाधान वास्तव में सरल है, लेकिन मुझे लगता है कि हम थोड़ा बेहतर कर सकते हैं।
Async EventHandlers को बेहतर बनाने के लिए ए (थोड़ा) फैनसीयर दृष्टिकोण
एक सुधार जो मुझे लगता है कि हम प्रारंभिक रूप से प्रस्तावित समाधान पर कर सकते हैं, वह यह है कि हम इसे थोड़ा और स्पष्ट कर सकते हैं कि हमारे पास एक एसिंक्स इवेंटहैंडलर है जो अपवादों को बुदबुदाने से सुरक्षित होना चाहिए।
यह दृष्टिकोण समय के साथ कोड बहाव को ईवेंट हैंडलर के बाहर चलने से समस्याग्रस्त कोड को रोकने से भी रोकेगा। हालाँकि, यह इस तथ्य को संबोधित नहीं करेगा कि आपको इसे मैन्युअल रूप से जोड़ने के लिए याद रखने की आवश्यकता है!
आइए कोड देखें:
static class EventHandlers { public static EventHandler<TArgs> TryAsync<TArgs>( Func<object, TArgs, Task> callback, Action<Exception> errorHandler) where TArgs : EventArgs => TryAsync<TArgs>( callback, ex => { errorHandler.Invoke(ex); return Task.CompletedTask; }); public static EventHandler<TArgs> TryAsync<TArgs>( Func<object, TArgs, Task> callback, Func<Exception, Task> errorHandler) where TArgs : EventArgs { return new EventHandler<TArgs>(async (object s, TArgs e) => { try { await callback.Invoke(s, e); } catch (Exception ex) { await errorHandler.Invoke(ex); } }); } }
अपवादों को एसिंक्स शून्य सीमा को पार करने से रोकने के लिए उपरोक्त कोड काफी हद तक सटीक समान दृष्टिकोण का उपयोग करता है। हम केवल ईवेंट हैंडलर के शरीर के चारों ओर पकड़ने की कोशिश करते हैं, लेकिन अब हमने इसे पुन: उपयोग करने के लिए एक स्पष्ट रूप से समर्पित विधि में बांधा है।
यहां बताया गया है कि इसे कैसे लागू किया जाएगा:
someEventRaisingObject.TheEvent += EventHandlers.TryAsync<EventArgs>( async (s, e) => { Console.WriteLine("Starting the event handler..."); await SomeTaskToAwait(); Console.WriteLine("Event handler completed."); }, ex => Console.WriteLine($"[TryAsync Error Callback] Our exception handler caught: {ex}"));
हम देख सकते हैं कि अब हमारे पास काम करने के लिए एक async टास्क सिग्नेचर वाला एक प्रतिनिधि है, और जो कुछ भी हम अंदर रखते हैं, हम आश्वस्त रहते हैं कि हमने पहले जो हेल्पर मेथड देखी थी, उसमें उसके आसपास ट्राई/कैच होगा।
यहां एक स्क्रीनशॉट दिखाया गया है जो त्रुटि हैंडलर कॉलबैक अपवाद को ठीक से कैप्चर कर रहा है:
पेशेवरों:
- अभी भी बहुत सरल है। रैपर फ़ंक्शन *थोड़ा* अधिक जटिल है, लेकिन फिर भी बहुत बुनियादी है।
- किसी पैकेज की आवश्यकता नहीं है।
- इसके कार्य करने के लिए आपको उस वर्ग का स्वामी होने की आवश्यकता नहीं है जो ईवेंट को बढ़ाता है। इसका मतलब यह है कि यह दृष्टिकोण WinForms और WPF UI घटकों सहित सभी मौजूदा ईवेंट-राइजिंग ऑब्जेक्ट्स के लिए काम करेगा।
- ईवेंट के लिए हैंडलर को हुक करते समय सिंटैक्स के कारण async EventHandlers के साथ काम करने का इरादा अधिक स्पष्ट है।
- कोड बहाव जो अंततः अधिक अपवाद फेंकता है, अभी भी कोशिश/पकड़ के अंदर लपेटा जाएगा
दोष:
- आपको अभी भी इस चीज़ को जोड़ने के लिए याद रखना होगा!
Async EventHandlers पर समापन विचार
जबकि मूल रूप से मैं तलाशने के लिए निकला था
इस लेख में, हमने पता लगाया कि मैं क्या तर्क दे सकता हूं कि आपके async EventHandlers को ठीक से व्यवहार करने का सबसे सरल तरीका है, और परिष्कृत समाधान (मेरी राय में) में केवल यह दोष है कि आपको इसका उपयोग करने के लिए याद रखने की आवश्यकता है।
एक टिप्पणीकार ने सुझाव दिया था कि कोई खोज कर सकता है
कुछ संकलन-समय एओपी ढांचे मौजूद हैं, लेकिन मैं इसे आपके लिए पाठक के रूप में एक अभ्यास के रूप में छोड़ दूंगा (क्योंकि यह मेरे लिए अनुवर्ती अभ्यास भी है)।