इवेंट हैंडलर मेमोरी लीक से क्यों और कैसे बचें?


154

मुझे सिर्फ एहसास हुआ, StackOverflow पर कुछ सवाल और जवाब पढ़कर, कि +=C # (या मुझे लगता है, अन्य .net भाषाओं) का उपयोग करके इवेंट हैंडलर जोड़ने से आम मेमोरी लीक हो सकती है ...

मैंने पिछले कई बार इस तरह के इवेंट हैंडलर का उपयोग किया है, और कभी भी महसूस नहीं किया कि वे मेरे अनुप्रयोगों में कारण, या स्मृति रिसाव हो सकते हैं।

यह कैसे काम करता है (मतलब, यह वास्तव में मेमोरी लीक का कारण क्यों बनता है)?
मैं इस समस्या को कैसे ठीक करुं ? उपयोग कर रहा है -=एक ही ईवेंट हैंडलर पर्याप्त के लिए?
क्या इस तरह की स्थितियों से निपटने के लिए सामान्य डिजाइन पैटर्न या सर्वोत्तम अभ्यास हैं?
उदाहरण: मुझे एक आवेदन को कैसे संभालना है जिसमें कई अलग-अलग धागे हैं, यूआई पर कई घटनाओं को उठाने के लिए कई अलग-अलग इवेंट हैंडलर का उपयोग कर रहे हैं?

क्या पहले से निर्मित बड़े एप्लिकेशन में इसे कुशलतापूर्वक मॉनिटर करने के लिए कोई अच्छा और सरल तरीका है?

जवाबों:


188

कारण स्पष्ट करने के लिए सरल है: जबकि एक ईवेंट हैंडलर सब्सक्राइब किया जाता है, ईवेंट का प्रकाशक ईवेंट हैंडलर डेलीगेट के माध्यम से सब्सक्राइबर के लिए एक संदर्भ रखता है (प्रतिनिधि को एक उदाहरण विधि है)।

यदि प्रकाशक ग्राहक से अधिक समय तक रहता है, तो वह ग्राहक को जीवित रखेगा, जबकि ग्राहक के पास कोई अन्य संदर्भ न हो।

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

यह है एक संभावित कारण ... लेकिन मेरे अनुभव में यह नहीं बल्कि अति-प्रचार है। आपका माइलेज अलग-अलग हो सकता है, बेशक ... आपको बस सावधान रहने की जरूरत है।


... मैंने कुछ लोगों को इस बारे में सवालों के जवाबों पर लिखते देखा है जैसे ".net में सबसे आम मेमोरी लीक क्या है"।
गिलेब

32
प्रकाशक की ओर से इसे प्राप्त करने का एक तरीका यह है कि आप एक बार यह सुनिश्चित करने के लिए घटना को शून्य पर सेट करें कि आप इसे आग नहीं लगेंगे। यह अंतर्निहित रूप से सभी ग्राहकों को हटा देगा, और उपयोगी हो सकता है जब कुछ घटनाओं को केवल वस्तु के जीवनकाल के कुछ चरणों के दौरान निकाल दिया जाता है।
JSB JS

2
घटना को अशक्त करने के लिए डिपोज़ विधि एक अच्छा क्षण होगा
डेवी फ़िएमेन्गी

6
@DaviF109ghi: यदि कुछ निपटाया जा रहा है तो ठीक है, यह कम से कम एक संभावित संकेत है कि यह जल्द ही कचरा संग्रहण के लिए योग्य होने जा रहा है, जिस बिंदु पर यह कोई फर्क नहीं पड़ता कि ग्राहक क्या हैं।
जॉन स्कीट

1
@ BrainSlugs83: "और विशिष्ट ईवेंट पैटर्न में वैसे भी एक प्रेषक शामिल है" - हाँ, लेकिन यह ईवेंट निर्माता है । आमतौर पर घटना ग्राहक उदाहरण प्रासंगिक है, और प्रेषक नहीं है। तो हाँ, यदि आप स्थैतिक विधि का उपयोग करके सदस्यता ले सकते हैं, तो यह कोई समस्या नहीं है - लेकिन मेरे अनुभव में शायद ही कोई विकल्प हो।
जॉन स्कीट

13

हां, -=पर्याप्त है, हालांकि, कभी भी सौंपे गए प्रत्येक कार्यक्रम का ट्रैक रखना काफी कठिन हो सकता है। (विस्तार के लिए, जॉन की पोस्ट देखें)। डिजाइन पैटर्न के बारे में, कमजोर ईवेंट पैटर्न पर एक नजर है ।


1
msdn.microsoft.com/en-us/library/aa970850(v=vs.100).aspx 4.0 संस्करण में अभी भी यह है।
फेमरेफ

अगर मुझे पता है कि एक प्रकाशक ग्राहक से अधिक समय तक रहने वाला है, तो मैं ग्राहक बनाता हूं IDisposableऔर घटना से हटा देता हूं ।
शमी वेइटहंडलर

9

मैंने https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 पर एक ब्लॉग में इस भ्रम की व्याख्या की है । मैं इसे यहाँ संक्षेप में प्रस्तुत करने का प्रयास करूँगा ताकि आपको एक स्पष्ट विचार आ सके।

संदर्भ का अर्थ है, "आवश्यकता":

सबसे पहले, आपको यह समझने की ज़रूरत है कि, यदि वस्तु A, B के संदर्भ में है, तो, इसका मतलब होगा, ऑब्जेक्ट A को कार्य करने के लिए ऑब्जेक्ट B की आवश्यकता है, है ना? इसलिए, कचरा कलेक्टर ऑब्जेक्ट B को तब तक एकत्रित नहीं करेगा जब तक कि मेमोरी में ऑब्जेक्ट A जीवित है।

मुझे लगता है कि यह हिस्सा एक डेवलपर के लिए स्पष्ट होना चाहिए।

+ = का अर्थ है, बाईं ओर की वस्तु पर राइट साइड ऑब्जेक्ट का संदर्भ इंजेक्ट करना:

लेकिन, भ्रम सी # + = ऑपरेटर से आता है। यह ऑपरेटर डेवलपर को स्पष्ट रूप से नहीं बताता है कि, इस ऑपरेटर का दाहिना हाथ वास्तव में बाईं ओर की वस्तु का संदर्भ इंजेक्ट कर रहा है।

यहाँ छवि विवरण दर्ज करें

और ऐसा करने से, ऑब्जेक्ट A सोचता है, उसे ऑब्जेक्ट B की आवश्यकता है, भले ही, आपके दृष्टिकोण से, ऑब्जेक्ट A को परवाह नहीं करनी चाहिए कि ऑब्जेक्ट B रहता है या नहीं। जैसे वस्तु A सोचती है कि वस्तु B की आवश्यकता है, वस्तु A, B को वस्तु A से बचाती है, जब तक कि वस्तु A जीवित है। लेकिन, यदि आप नहीं चाहते कि इवेंट सब्सक्राइबर ऑब्जेक्ट को दी गई सुरक्षा, तो, आप कह सकते हैं, एक मेमोरी लीक हुई।

यहाँ छवि विवरण दर्ज करें

इवेंट हैंडलर को बंद करके आप इस तरह के लीक से बच सकते हैं।

निर्णय कैसे लें?

लेकिन, आपके पूरे कोड-बेस में बहुत सारे ईवेंट और इवेंट हैंडलर हैं। क्या इसका मतलब है, आपको हर जगह इवेंट हैंडलर रखने की ज़रूरत है? इसका उत्तर है, यदि आपको ऐसा करना है, तो आपका कोडबेस क्रिया के साथ वास्तव में बदसूरत होगा।

आप यह निर्धारित करने के लिए एक साधारण प्रवाह चार्ट का पालन कर सकते हैं कि क्या एक कोचिंग इवेंट हैंडलर आवश्यक है या नहीं।

यहाँ छवि विवरण दर्ज करें

अधिकांश समय, आपको लग सकता है कि इवेंट सब्सक्राइबर ऑब्जेक्ट इवेंट पब्लिशर ऑब्जेक्ट जितना ही महत्वपूर्ण है और दोनों एक ही समय में रहने वाले हैं।

ऐसे परिदृश्य का उदाहरण जहां आपको चिंता करने की आवश्यकता नहीं है

उदाहरण के लिए, एक बटन एक खिड़की की घटना पर क्लिक करें।

यहाँ छवि विवरण दर्ज करें

यहां, इवेंट प्रकाशक बटन है, और इवेंट सब्सक्राइबर मेनविंडो है। उस फ्लो चार्ट को लागू करते हुए, एक प्रश्न पूछें, क्या मेन विंडो (ईवेंट सब्सक्राइबर) बटन (इवेंट पब्लिशर) से पहले मृत होना चाहिए? जाहिर है कि नहीं? यह भी मतलब नहीं होगा। फिर, क्लिक इवेंट हैंडलर की कोचिंग के बारे में चिंता क्यों करें?

एक उदाहरण जब एक घटना हैंडलर टुकड़ी एक जरूरी है।

मैं एक उदाहरण प्रदान करूंगा, जहां प्रकाशक ऑब्जेक्ट से पहले ग्राहक ऑब्जेक्ट मृत माना जाता है। कहते हैं, आपका MainWindow "SomeHappened" नामक एक घटना प्रकाशित करता है और आप एक बटन क्लिक करके मुख्य विंडो से एक बच्चा विंडो दिखाते हैं। चाइल्ड विंडो मुख्य विंडो के उस इवेंट को सब्सक्राइब करती है।

यहाँ छवि विवरण दर्ज करें

और, चाइल्ड विंडो मेन विंडो के एक इवेंट को सब्सक्राइब करती है।

यहाँ छवि विवरण दर्ज करें

इस कोड से, हम स्पष्ट रूप से समझ सकते हैं कि मेन विंडो में एक बटन है। उस बटन पर क्लिक करने पर एक बाल खिड़की दिखाई देती है। चाइल्ड विंडो मुख्य विंडो से एक ईवेंट सुनता है। कुछ करने के बाद, उपयोगकर्ता चाइल्ड विंडो बंद कर देता है।

अब, यदि आप एक प्रश्न पूछते हैं, तो प्रवाह चार्ट के अनुसार, क्या मैं (प्रकाशक) मुख्य विंडो से पहले बच्चे की खिड़की (इवेंट सब्सक्राइबर) मृत होना चाहिए? जवाब हां होना चाहिए। सही है? तो, इवेंट हैंडलर को अलग करें। .मैं आमतौर पर विंडो के अनलोडेड इवेंट से करता हूं।

अंगूठे का एक नियम: यदि आपका दृश्य (यानी WPF, WinForm, UWP, Xamarin फॉर्म, आदि) एक ViewModel की घटना के लिए सदस्यता लेता है, तो हमेशा घटना हैंडलर को अलग करने के लिए याद रखें। क्योंकि एक ViewModel आमतौर पर एक दृश्य से अधिक समय तक रहता है। इसलिए, अगर ViewModel को नष्ट नहीं किया गया है, तो उस ViewModel की सदस्यता वाली कोई भी घटना स्मृति में रहेगी, जो अच्छा नहीं है।

एक मेमोरी प्रोफाइलर का उपयोग करके अवधारणा का प्रमाण।

यह बहुत मजेदार नहीं होगा अगर हम एक मेमोरी प्रोफाइलर के साथ अवधारणा को मान्य नहीं कर सकते हैं। मैंने इस प्रयोग में JetBrain dotMemory प्रोफाइलर का उपयोग किया है।

सबसे पहले, मैंने मेनविंडो चलाया है, जो इस तरह दिखाता है:

यहाँ छवि विवरण दर्ज करें

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

यहाँ छवि विवरण दर्ज करें

ठीक है, फिर, मैंने इवेंट हैंडलर को अलग कर दिया जैसा कि नीचे दिखाया गया है।

यहाँ छवि विवरण दर्ज करें

फिर, मैंने उसी चरणों का प्रदर्शन किया है और मेमोरी प्रोफाइलर की जांच की है। इस बार, वाह! कोई और अधिक स्मृति रिसाव।

यहाँ छवि विवरण दर्ज करें


3

एक घटना वास्तव में घटना संचालकों की एक लिंक्ड सूची है

जब आप इस घटना पर + = नया ईवेंटहैंडलर करते हैं तो इससे कोई फर्क नहीं पड़ता कि इस विशेष फ़ंक्शन को पहले श्रोता के रूप में जोड़ा गया है, यह प्रति + एक बार जोड़ा जाएगा।

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

यहाँ देखें

और यहाँ MSDN


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.