सी # इवेंट्स एंड थ्रेड सेफ्टी


237

अपडेट करें

C # 6 के रूप में, इस प्रश्न का उत्तर है:

SomeEvent?.Invoke(this, e);

मैं अक्सर निम्नलिखित सलाह सुनता / पढ़ता हूं:

इससे पहले कि आप इसकी जांच करें nullऔर आग लगा दें, हमेशा किसी घटना की प्रतिलिपि बनाएँ । यह थ्रेडिंग के साथ एक संभावित समस्या को समाप्त कर देगा जहां घटना nullठीक उसी स्थान पर हो जाती है , जहां आप नल की जांच करते हैं और जहां आप घटना को फायर करते हैं:

// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;

if (copy != null)
    copy(this, EventArgs.Empty); // Call any handlers on the copied list

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

लेकिन इस बीच, इस मुद्दे को भी होने के लिए, किसी अन्य सूत्र ने कुछ इस तरह किया होगा:

// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...

वास्तविक अनुक्रम यह मिश्रण हो सकता है:

// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;

// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;    
// Good, now we can be certain that OnTheEvent will not run...

if (copy != null)
    copy(this, EventArgs.Empty); // Call any handlers on the copied list

वह बिंदु OnTheEventजो लेखक के अनसब्सक्राइब होने के बाद चलता है, और फिर भी वे ऐसा होने से बचने के लिए विशेष रूप से अनसब्सक्राइब करते हैं। निश्चित रूप से वास्तव में जिस चीज की आवश्यकता होती है वह है कस्टम एक्सेस इम्प्लीमेंटेशन जिसमें addऔर एक्सेसर्स में उपयुक्त सिंक्रोनाइज़ेशन होता है remove। और इसके अलावा अगर किसी घटना को निकाल दिया जाता है तो ताला बंद होने पर संभावित गतिरोध की समस्या होती है।

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

(और क्या यह केवल delegate { }सदस्य घोषणा पर खाली असाइन करने के लिए बहुत आसान नहीं है ताकि आपको nullपहली बार जांचने की आवश्यकता न हो ?)

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

तो मैं यह मानता हूं कि यह कॉपी / चेक मुहावरा है कि कार्गो पंथ प्रोग्रामिंग है, जो आपके कोड में गड़बड़ी और शोर जोड़ रहा है। वास्तव में अन्य धागों से बचाने के लिए बहुत अधिक कार्य की आवश्यकता होती है।

एरिक लिपर्ट के ब्लॉग पोस्ट के जवाब में अपडेट:

इसलिए एक बड़ी बात यह है कि मैं इवेंट हैंडलर्स के बारे में याद कर रहा था: "इवेंट हैंडलर को इवेंट के अनसब्सक्राइब होने के बाद भी कॉल करने के लिए मजबूत होना आवश्यक है", और जाहिर है इसलिए हमें केवल इवेंट की संभावना के बारे में ध्यान रखना होगा प्रतिनिधि होना nullक्या घटना संचालकों पर कहीं भी आवश्यकता है?

और इसलिए: "इस समस्या को हल करने के अन्य तरीके हैं; उदाहरण के लिए, हैंडलर को खाली कार्रवाई करने के लिए आरंभीकरण करना जो कभी भी हटाया नहीं जाता है। लेकिन एक अशक्त जांच करना मानक पैटर्न है।"

तो मेरे सवाल का एक शेष टुकड़ा है, स्पष्ट-अशक्त "मानक पैटर्न" की जांच क्यों है? विकल्प, खाली प्रतिनिधि को असाइन करने के लिए, केवल = delegate {}ईवेंट घोषणा में जोड़ा जाना आवश्यक है , और यह उन हर जगह से बदबूदार समारोह के छोटे ढेर को समाप्त करता है जहां घटना को उठाया जाता है। यह सुनिश्चित करना आसान होगा कि खाली प्रतिनिधि तत्काल करने के लिए सस्ता है। या मैं अभी भी कुछ याद कर रहा हूँ?

निश्चित रूप से यह होना चाहिए कि (जैसा कि जॉन स्कीट ने सुझाव दिया) यह सिर्फ .NET 1.x सलाह है जो मर नहीं गई है, जैसा कि इसे 2005 में किया जाना चाहिए था?


4
यह सवाल कुछ समय पहले एक आंतरिक चर्चा में आया था; मैं पिछले कुछ समय से इसे ब्लॉग करने का इरादा कर रहा हूं। इस विषय पर मेरी पोस्ट यहाँ है: घटनाक्रम और दौड़
एरिक लिपर्ट

3
स्टीफन क्लीरी के पास कोडप्रोजेक्ट लेख है जो इस मुद्दे की जांच करता है, और वह एक सामान्य उद्देश्य का निष्कर्ष निकालता है, "थ्रेड-सेफ" समाधान मौजूद नहीं है। मूल रूप से यह घटना के आक्रमणकारी पर निर्भर करता है कि प्रतिनिधि यह सुनिश्चित करने के लिए अशक्त नहीं है, और इवेंट हैंडलर तक यह सदस्यता समाप्त होने के बाद आह्वान किया जा सकता है।
rkagerer

3
@rkagerer - वास्तव में दूसरे मुद्दे को कभी-कभी इवेंट हैंडलर से निपटना पड़ता है, भले ही थ्रेड्स शामिल न हों। यदि कोई ईवेंट हैंडलर किसी अन्य हैंडलर को वर्तमान में नियंत्रित किए जा रहे ईवेंट से अनसब्सक्राइब करने के लिए कहता है , लेकिन उस दूसरे सब्सक्राइबर को वैसे भी इवेंट प्राप्त होगा (क्योंकि हैंडलिंग के दौरान यह सदस्यता समाप्त हो गई थी)।
डैनियल ईयरविकर

3
शून्य ग्राहकों के साथ किसी ईवेंट की सदस्यता जोड़ना, ईवेंट के लिए एकमात्र सब्सक्रिप्शन को हटाना, शून्य ग्राहकों के साथ किसी ईवेंट को आमंत्रित करना और बिल्कुल एक सब्सक्राइबर के साथ किसी ईवेंट को लागू करना, अन्य नंबरों को शामिल करने / हटाने / इनवॉइस परिदृश्यों की तुलना में सभी बहुत तेज़ संचालन हैं ग्राहकों। एक डमी प्रतिनिधि को जोड़ने से आम मामला धीमा हो जाता है। C # के साथ वास्तविक समस्या यह है कि इसके रचनाकारों ने EventName(arguments)इस घटना के प्रतिनिधि को बिना शर्त आमंत्रित करने का फैसला किया , बजाय इसके कि यह केवल प्रतिनिधि को आमंत्रित करे अगर गैर-अशक्त (अशक्त होने पर कुछ भी न करें)।
सुपरकैट

जवाबों:


100

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

वाष्पशील संशोधक के बिना यह संभव है कि ली गई स्थानीय प्रतिलिपि पुराना हो जाए, लेकिन यह सब है। यह एक कारण नहीं होगा NullReferenceException

और हाँ, वहाँ निश्चित रूप से एक दौड़ की स्थिति है - लेकिन हमेशा रहेगा। मान लीजिए कि हम कोड को केवल इसमें बदलते हैं:

TheEvent(this, EventArgs.Empty);

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

एक खाली प्रतिनिधि का उपयोग करना निश्चित रूप से अशक्तता की जांच से बचता है, लेकिन दौड़ की स्थिति को ठीक नहीं करता है। यह भी गारंटी नहीं देता है कि आप हमेशा चर के नवीनतम मूल्य को "देखें"।


4
जो डफी के "विंडोज पर समवर्ती प्रोग्रामिंग" जेआईटी अनुकूलन और प्रश्न के मेमोरी मॉडल पहलू को कवर करता है; code.logos.com/blog/2008/11/events_and_threads_part_4.html
ब्रैडली

2
मैंने इसे "मानक" सलाह के बारे में टिप्पणी के आधार पर स्वीकार किया है, जो कि पूर्व-सी # 2 है, और मैं किसी के विरोधाभासी नहीं सुन रहा हूं। जब तक कि आपके ईवेंट आर्गन्स को तुरंत समाप्त करना महंगा नहीं होता है, तो बस अपने ईवेंट डिक्लेरेशन के अंत में '= डेलिगेट {}' डालें और फिर अपने ईवेंट को सीधे कॉल करें जैसे कि वे विधियाँ हैं; कभी उन्हें अशक्त न करें। (हैंडलर सुनिश्चित करने के बारे में मैं जो अन्य सामान लाया था, उसे डीलिस्टिंग के बाद नहीं कहा जाता है, यह सब अप्रासंगिक था, और यह सुनिश्चित करना भी असंभव है, यहां तक ​​कि सिंगल थ्रेडेड कोड के लिए भी, उदाहरण के लिए यदि हैंडलर 1 हैंडलर 2 को डीलिस्ट करने के लिए कहता है, हैंडलर 2 अभी भी कहा जाएगा अगला।)
डैनियल इयरविकर

2
एकमात्र समस्या का मामला (हमेशा की तरह) संरचना है, जहां आप यह सुनिश्चित नहीं कर सकते हैं कि उनके सदस्यों में अशक्त मूल्यों के अलावा किसी भी चीज के साथ उन्हें तत्काल किया जाएगा। लेकिन संरचनाएं चूसती हैं।
डैनियल इयरविकर

1
खाली प्रतिनिधि के बारे में, यह प्रश्न भी देखें: stackoverflow.com/questions/170907/…
व्लादिमीर

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

52

मैं बहुत से लोगों को ऐसा करने की विस्तार पद्धति की ओर जाता देख रहा हूं ...

public static class Extensions   
{   
  public static void Raise<T>(this EventHandler<T> handler, 
    object sender, T args) where T : EventArgs   
  {   
    if (handler != null) handler(sender, args);   
  }   
}

यह आपको घटना को बढ़ाने के लिए वाक्यविन्यास देता है ...

MyEvent.Raise( this, new MyEventArgs() );

और भी स्थानीय प्रति के साथ दूर करता है क्योंकि यह विधि कॉल समय पर कब्जा कर लिया है।


9
मुझे सिंटैक्स पसंद है, लेकिन चलो स्पष्ट हो ... यह अपठित होने के बाद भी एक बासी हैंडलर के साथ समस्या को हल नहीं करता है। यह केवल अशांति की समस्या को हल करता है। हालांकि मुझे वाक्य रचना पसंद है, मैं सवाल करता हूं कि क्या यह वास्तव में किसी भी तरह से बेहतर है: सार्वजनिक घटना EventHandler <T> MyEvent = delete "}; ... MyEvent (यह, नया MyEventArgs ()); यह भी एक बहुत कम घर्षण समाधान है जो मुझे इसकी सादगी के लिए पसंद है।
साइमन गिल्बी

@ साइमन मैं अलग-अलग लोगों को इस पर अलग-अलग बातें कहते हुए देखता हूं। मैंने इसका परीक्षण किया है और जो मैंने किया है वह मुझे संकेत देता है कि यह अशक्त हैंडलर मुद्दे को संभालता है। भले ही मूल सिंक हैंडलर के बाद घटना से अपंजीकृत हो! = अशक्त चेक, ईवेंट अभी भी उठाया गया है और कोई अपवाद नहीं फेंका गया है।
जेपी अलीतो


1
+1। मैंने सिर्फ यह तरीका खुद लिखा, थ्रेड-सेफ्टी के बारे में सोचना शुरू किया, कुछ शोध किए और इस सवाल पर अड़ गए।
नील्स वैन डेर रेस्ट

इसे VB.NET से कैसे कहा जा सकता है? या? RaiseEvent ’मल्टी-थ्रेडिंग परिदृश्यों के लिए पहले से ही पूरा करता है?

35

"स्पष्ट-अशक्त 'मानक पैटर्न' क्यों है?"

मुझे संदेह है कि इसका कारण यह हो सकता है कि नल-जांच अधिक प्रदर्शनकारी है।

यदि आप हमेशा अपने इवेंट के लिए किसी खाली प्रतिनिधि को सब्सक्राइब करते हैं, जब वे बनाए जाते हैं, तो कुछ ओवरहेड्स होंगे:

  • खाली प्रतिनिधि के निर्माण की लागत।
  • इसे शामिल करने के लिए एक प्रतिनिधि श्रृंखला के निर्माण की लागत।
  • हर बार घटना के उठने पर व्यर्थ प्रतिनिधि को आमंत्रित करने की लागत।

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

मैंने सदस्यता-खाली-प्रतिनिधि दृष्टिकोण के प्रभाव को देखने के लिए कुछ सरसरी प्रदर्शन परीक्षण किया, और यहाँ मेरे परिणाम हैं:

Executing 50000000 iterations . . .
OnNonThreadSafeEvent took:      432ms
OnClassicNullCheckedEvent took: 490ms
OnPreInitializedEvent took:     614ms <--
Subscribing an empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took:      674ms
OnClassicNullCheckedEvent took: 674ms
OnPreInitializedEvent took:     2041ms <--
Subscribing another empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took:      2011ms
OnClassicNullCheckedEvent took: 2061ms
OnPreInitializedEvent took:     2246ms <--
Done

ध्यान दें कि शून्य या एक सब्सक्राइबर के मामले में (UI नियंत्रणों के लिए सामान्य, जहां ईवेंट्स बहुतायत से होते हैं), एक खाली प्रतिनिधि के साथ आरंभिक घटना उल्लेखनीय रूप से धीमी है (50 मिलियन से अधिक पुनरावृत्तियों ...)

अधिक जानकारी और स्रोत कोड के लिए, इस ब्लॉग पोस्ट को .NET इवेंट इनवोकेशन थ्रेड सेफ्टी पर जाएँ, जो मैंने इस प्रश्न के पूछे जाने के ठीक एक दिन पहले प्रकाशित किया था (!)

(मेरा परीक्षण सेट-अप त्रुटिपूर्ण हो सकता है इसलिए स्रोत कोड डाउनलोड करने के लिए स्वतंत्र महसूस करें और स्वयं इसका निरीक्षण करें। किसी भी प्रतिक्रिया की बहुत सराहना की जाती है।)


8
मुझे लगता है कि आप अपने ब्लॉग पोस्ट में मुख्य बिंदु बनाते हैं: प्रदर्शन के निहितार्थ के बारे में चिंता करने की कोई आवश्यकता नहीं है जब तक कि यह एक अड़चन नहीं है। क्यों बदसूरत रास्ता अनुशंसित तरीका है? यदि हम स्पष्टता के बजाय समय से पहले अनुकूलन चाहते थे, तो हम कोडांतरक का उपयोग कर रहे थे - इसलिए मेरा प्रश्न बना हुआ है, और मुझे लगता है कि संभावित उत्तर यह है कि सलाह बस गुमनाम प्रतिनिधियों को पूर्व निर्धारित करती है और मानव संस्कृति को पुरानी सलाह को स्थानांतरित करने में लंबा समय लगता है, जैसे प्रसिद्ध "पॉट रोस्ट स्टोरी" में।
डैनियल ईयरविकर

13
और आपके आंकड़े बहुत अच्छी तरह से इस बात को साबित करते हैं: ओवरहेड को ऊपर उठाए गए प्रति घटना (प्री-इनिट बनाम क्लासिक-नल) के अनुसार, सिर्फ ढाई नैनोएएनएसईसीएनडीएस (!!!) तक नीचे आता है। ऐसा करने के लिए वास्तविक कार्य के साथ लगभग ऐप्स में यह undetectable होगा, लेकिन यह देखते हुए कि ईवेंट उपयोग का विशाल बहुमत GUI चौखटे में है, आपको इसकी तुलना विन्फ़ॉर्म, आदि में किसी स्क्रीन के पुर्ज़े के हिस्से की लागत के साथ करनी होगी। यह वास्तविक सीपीयू कार्य के प्रलय में और भी अधिक अदृश्य हो जाता है और संसाधनों की प्रतीक्षा करता है। आप वैसे भी कड़ी मेहनत के लिए मुझसे +1 लेते हैं। :)
डैनियल इयरविकर

1
@DanielEarwicker ने कहा कि यह सही है, आपने मुझे सार्वजनिक कार्यक्रम WrapperDoneHandler OnWrapperDone = (x, y) => {} में एक आस्तिक होने के लिए स्थानांतरित किया है; नमूना।
मिकी पेरेलस्टीन

1
उन मामलों में Delegate.Combine/ Delegate.Removeजोड़ी के लिए समय अच्छा हो सकता है जहां घटना शून्य है, एक, या दो ग्राहक; यदि कोई एक ही प्रतिनिधि उदाहरण को बार-बार जोड़ता है और निकालता है, तो मामलों के बीच लागत अंतर विशेष रूप से स्पष्ट हो जाएगा क्योंकि Combineतेजी से विशेष-मामला व्यवहार होता है जब एक तर्क है null(बस दूसरे को लौटाएं), और Removeबहुत तेज है जब दो तर्क होते हैं बराबर (बस वापसी नल)।
सुपरकाट

12

मैं वास्तव में इस पढ़ने का आनंद लिया - नहीं! भले ही मुझे C # सुविधा के साथ काम करने की आवश्यकता है जिसे इवेंट कहा जाता है!

संकलक में इसे ठीक क्यों नहीं करें? मुझे पता है कि एमएस लोग हैं जो इन पदों को पढ़ते हैं, इसलिए कृपया इसे लौ न करें!

1 - अशक्त समस्या ) घटनाओं को क्यों नहीं बनाया जाए? पहली जगह में अशक्त होने के बजाय खाली करें? शून्य चेक के लिए कोड की कितनी लाइनों को बचाया जाएगा या = delegate {}घोषणा पर चिपका दिया जाएगा ? संकलक को खाली मामले को संभालने दें, IE कुछ नहीं करें! यदि यह घटना के निर्माता के लिए सभी मायने रखता है, तो वे जांच कर सकते हैं। खाली करें और जो कुछ भी वे इसकी देखभाल करते हैं वह करें! अन्यथा सभी अशक्त चेक / डेलीगेट कहते हैं कि समस्या के आसपास हैक हैं!

ईमानदारी से मैं हर घटना के साथ ऐसा करने के लिए थक गया हूँ - उर्फ ​​बॉयलरप्लेट कोड!

public event Action<thisClass, string> Some;
protected virtual void DoSomeEvent(string someValue)
{
  var e = Some; // avoid race condition here! 
  if(null != e) // avoid null condition here! 
     e(this, someValue);
}

2 - दौड़ हालत मुद्दा ) मैं एरिक के ब्लॉग पोस्ट को पढ़ता हूं, मैं मानता हूं कि एच (हैंडलर) को तब संभालना चाहिए जब वह स्वयं को संदर्भित करता है, लेकिन क्या घटना को अपरिवर्तनीय / थ्रेड सुरक्षित नहीं बनाया जा सकता है? IE, इसके निर्माण पर एक लॉक फ्लैग सेट करें, ताकि जब भी इसे बुलाया जाए, तो यह सब सब्स्क्राइब्ड और अन-सब्स्क्राइबिंग को लॉक कर दे, जबकि इसका निष्पादन हो रहा हो?

निष्कर्ष ,

क्या आधुनिक दिन की भाषाएं हमारे लिए इन जैसी समस्याओं को हल करने वाली नहीं हैं?


सहमत थे, संकलक में इसके लिए बेहतर समर्थन होना चाहिए। तब तक, मैंने एक PostSharp पहलू बनाया , जो एक संकलन के बाद के चरण में ऐसा करता है । :)
स्टीवन ज्यूरिस

4
थ्रेड सदस्यता / सदस्यता रद्द करने के अनुरोध को ब्लॉक करना, जबकि बाहर कोड को पूरा करने के लिए मनमाने ढंग से इंतजार करना सब्सक्राइबर्स को रद्द करने के बाद घटनाओं को प्राप्त करने की तुलना में कहीं अधिक खराब है, खासकर जब से बाद की "समस्या" को आसानी से हल किया जा सकता है, केवल इवेंट हैंडलर एक झंडे की जांच कर सकते हैं कि क्या देखने के लिए वे अभी भी अपनी घटना प्राप्त करने में रुचि रखते हैं, लेकिन पूर्व डिजाइन से उत्पन्न गतिरोध अचूक हो सकता है।
सुपरकैट

@supercat। Imo, "बहुत बुरा" टिप्पणी सुंदर आवेदन पर निर्भर है। जब एक विकल्प के अतिरिक्त झंडे के बिना बहुत सख्त लॉकिंग कौन नहीं चाहेगा? एक गतिरोध केवल तब होना चाहिए जब इवेंट हैंडलिंग थ्रेड अभी तक प्रतीक्षा कर रहा हो, क्योंकि लॉक एक ही थ्रेड फिर से दर्ज किए गए हैं और मूल ईवेंट हैंडलर के भीतर एक सदस्यता / सदस्यता समाप्त नहीं होगी। अगर किसी इवेंट हैंडलर के हिस्से के रूप में क्रॉस थ्रेड वेटिंग है जो डिजाइन का एक हिस्सा होगा जिसे मैं फिर से काम करना पसंद करूंगा। मैं एक सर्वर-साइड ऐप एंगल से आ रहा हूं, जिसमें प्रेडिक्टेबल पैटर्न हैं।
क्रुकसेक

2
@ क्रोक्यूसेक: यह साबित करने के लिए आवश्यक विश्लेषण कि एक सिस्टम गतिरोध से मुक्त है अगर कोई निर्देशित ग्राफ में कोई भी चक्र नहीं होगा जो प्रत्येक ताले को सभी तालों से जोड़ता है जिसकी आवश्यकता तब हो सकती है जब इसे आयोजित किया जाए [चक्रों की कमी प्रणाली साबित होती है गतिरोध से मुक्त]। लॉक होने के दौरान मनमाने कोड लगाने की अनुमति देने से लॉक से किसी भी लॉक में "may-be-be-need" ग्राफ में एक बढ़त पैदा होगी, जो मनमाना कोड हासिल कर सकता है (सिस्टम में हर लॉक नहीं, लेकिन उससे बहुत दूर। )। चक्रों के परिणामस्वरूप अस्तित्व का अर्थ यह नहीं होगा कि गतिरोध होगा, लेकिन ...
सुपर

1
... यह साबित करने के लिए आवश्यक विश्लेषण के स्तर में बहुत वृद्धि होगी कि यह नहीं कर सका।
सुपरकैट

8

साथ सी # 6 और इसके बाद के संस्करण, कोड नए का उपयोग कर सरल किया जा सकता है ?.के रूप में ऑपरेटर:

TheEvent?.Invoke(this, EventArgs.Empty);

यहाँ MSDN प्रलेखन है।


5

C # के माध्यम से CLR पुस्तक में जेफरी रिक्टर के अनुसार , सही विधि है:

// Copy a reference to the delegate field now into a temporary field for thread safety
EventHandler<EventArgs> temp =
Interlocked.CompareExchange(ref NewMail, null, null);
// If any methods registered interest with our event, notify them
if (temp != null) temp(this, e);

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


हो सकता है कि मैं smth मिस कर रहा हूं, लेकिन Interlocked.CompareExchange NullReferenceException को फेंकता है यदि इसका पहला तर्क शून्य है जो कि वास्तव में हम बचना चाहते हैं। msdn.microsoft.com/en-us/library/bb297966.aspx
Kniganapolke

1
Interlocked.CompareExchangeविफल हो जाएगा अगर यह किसी तरह से एक अशक्त हो गया था ref, लेकिन यह refएक भंडारण स्थान (जैसे NewMail) के लिए पारित होने के रूप में एक ही बात नहीं है जो मौजूद है और जो शुरू में एक अशक्त संदर्भ रखता है।
सुपरकैट

4

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

private readonly object eventMutex = new object();

private event EventHandler _onEvent = null;

public event EventHandler OnEvent
{
  add
  {
    lock(eventMutex)
    {
      _onEvent += value;
    }
  }

  remove
  {
    lock(eventMutex)
    {
      _onEvent -= value;
    }
  }

}

private void HandleEvent(EventArgs args)
{
  lock(eventMutex)
  {
    if (_onEvent != null)
      _onEvent(args);
  }
}

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


वास्तव में, मैं देख रहा हूं कि कोई अन्य व्यक्ति यहां समान पैटर्न का उपयोग कर रहा है: stackoverflow.com/questions/3668953/…
ऐश

2

यह अभ्यास संचालन के एक निश्चित क्रम को लागू करने के बारे में नहीं है। यह वास्तव में एक अशक्त संदर्भ अपवाद से बचने के बारे में है।

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

नोट: दौड़ की स्थिति को ठीक करने में संभवतः एक समकालिक ध्वज ट्रैक का उपयोग करना शामिल है, चाहे हैंडलर चलना चाहिए


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

1
खैर यह मेरी बात थी। वे दौड़ की स्थिति के बारे में परवाह नहीं करते हैं। वे केवल अशक्त संदर्भ अपवाद की परवाह करते हैं। मैं इसे अपने उत्तर में संपादित करूंगा।
dss539

और मेरा कहना है: क्यों यह शून्य संदर्भ अपवाद के बारे में परवाह करने के लिए समझ में आता है और अभी तक दौड़ की स्थिति के बारे में परवाह नहीं है?
डैनियल ईयरविकर

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

2
@ dss539: जबकि कोई ईवेंट फ्रेमवर्क डिज़ाइन कर सकता है जो लंबित ईवेंट इनवोकेशन समाप्त होने के बाद तक अनसब्सक्राइब रिक्वेस्ट को ब्लॉक कर देगा, इस तरह की डिज़ाइन किसी भी ईवेंट (यहां तक ​​कि किसी इवेंट जैसी चीज़ Unload) के लिए किसी ऑब्जेक्ट के सब्सक्रिप्शन को अन्य इवेंट्स के लिए सुरक्षित रूप से रद्द करना असंभव बना देगा । बुरा। बेहतर यह है कि घटना को अनसब्सक्राइब करने के अनुरोध के कारण ईवेंट को "अंततः" अनसब्सक्राइब कर दिया जाएगा, और ईवेंट सब्सक्राइबर्स को यह जांचना चाहिए कि क्या उन्हें इनवॉइस किया गया है कि क्या उनके लिए कुछ उपयोगी है।
सुपरकैट

1

इसलिए मुझे यहां पार्टी करने में थोड़ी देर हो गई। :)

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

इसे ध्यान में रखते हुए, एक समाधान यह कहना है "अच्छी तरह से, शून्य ग्राहकों को शून्य द्वारा दर्शाया गया है।" फिर बस अपना महंगा ऑपरेशन करने से पहले अशक्त जांच करें। मुझे लगता है कि ऐसा करने का एक और तरीका यह होगा कि डेलीगेट टाइप पर एक काउंट प्रॉपर्टी हो, इसलिए आप केवल महंगा ऑपरेशन ही करेंगे अगर myDelegate.Count> 0. काउंट प्रॉपर्टी का उपयोग करना एक अच्छा पैटर्न है जो मूल समस्या को हल करता है अनुकूलन की अनुमति देने के लिए, और इसमें NullReferenceException पैदा किए बिना सक्षम होने की अच्छी संपत्ति भी है।

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

नोट: यह शुद्ध अटकलें हैं। मैं .NET भाषाओं या सीएलआर के साथ शामिल नहीं हूं।


मुझे लगता है कि आप "खाली प्रतिनिधि के उपयोग के बजाय इसका मतलब है ..." आप पहले से ही जो सुझाव देते हैं, उसे खाली कराटे के लिए इनिशियलाइज़ कर सकते हैं। परीक्षण (MyEvent.GetInvocationList ()। लंबाई == 1) सही होगा यदि प्रारंभिक खाली प्रतिनिधि सूची में केवल एक चीज है। अभी भी पहले एक प्रति बनाने की कोई आवश्यकता नहीं होगी। हालांकि मुझे लगता है कि आपके द्वारा वर्णित मामला वैसे भी अत्यंत दुर्लभ होगा।
डेनिएल इयरविकर

मुझे लगता है कि हम यहां प्रतिनिधियों और घटनाओं के विचारों का सामना कर रहे हैं। यदि मेरी कक्षा में कोई ईवेंट फू है, तो जब बाहरी उपयोगकर्ता MyType.Foo + = / - = को कॉल करते हैं, तो वे वास्तव में add_Foo () और remove_Foo () विधियों को कॉल कर रहे हैं। हालाँकि, जब मैं फू को उस वर्ग के भीतर से संदर्भित करता हूँ जहाँ यह परिभाषित किया गया है, तो मैं वास्तव में अंतर्निहित प्रतिनिधि को सीधे संदर्भित कर रहा हूँ, न कि add_Foo () और remove_Foo () विधियों से। और EventHandlerList जैसे प्रकारों के अस्तित्व के साथ, कुछ भी अनिवार्य नहीं है कि प्रतिनिधि और घटना समान स्थान पर हो। मेरी प्रतिक्रिया में "ध्यान रखें" पैराग्राफ का यही मतलब है।
लेवी

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

जैसा कि हम घटना को फायर करने के बारे में बात कर रहे हैं, मैं यह नहीं देख सकता कि क्यों ऐड / रिमूव एक्सेसर्स यहां महत्वपूर्ण हैं।
डैनियल इयरविकर

@ लेवी: मैं वास्तव में सी # घटनाओं को संभालने के तरीके को नापसंद करता हूं। अगर मेरे पास मेरे शराबी होते, तो प्रतिनिधि को घटना से अलग नाम दिया जाता। एक वर्ग के बाहर से, एक घटना के नाम पर केवल अनुमेय संचालन होगा +=और -=। वर्ग के भीतर, अनुमेय परिचालनों में इनवोकेशन (बिल्ट-इन नल चेक के साथ), परीक्षण के खिलाफ nullया सेटिंग शामिल होगी null। किसी अन्य चीज़ के लिए, किसी ऐसे प्रतिनिधि का उपयोग करना होगा जिसका नाम किसी विशेष उपसर्ग या प्रत्यय के साथ घटना का नाम होगा।
सुपरकैट

0

सिंगल थ्रेडेड एप्लिकेशंस के लिए, आप कर रहे हैं यह एक मुद्दा नहीं है।

हालांकि, यदि आप एक घटक बना रहे हैं जो घटनाओं को उजागर करता है, तो इस बात की कोई गारंटी नहीं है कि आपके घटक का कोई उपभोक्ता मल्टीथ्रेडिंग नहीं जा रहा है, इस स्थिति में आपको सबसे खराब तैयारी करने की आवश्यकता है।

खाली प्रतिनिधि का उपयोग करने से समस्या हल हो जाती है, लेकिन यह भी घटना के लिए हर कॉल पर एक प्रदर्शन हिट का कारण बनता है, और संभवतः जीसी निहितार्थ हो सकता है।

आप सही हैं कि ऐसा करने के लिए उपभोक्ता तीनों की सदस्यता समाप्त कर देता है, लेकिन यदि उन्होंने इसे अस्थायी प्रतिलिपि से आगे कर दिया है, तो पहले से ही पारगमन में संदेश पर विचार करें।

यदि आप अस्थायी चर का उपयोग नहीं करते हैं, और खाली प्रतिनिधि का उपयोग नहीं करते हैं, और कोई व्यक्ति सदस्यता समाप्त करता है, तो आपको एक शून्य संदर्भ अपवाद मिलता है, जो घातक है, इसलिए मुझे लगता है कि लागत इसके लायक है।


0

मैंने वास्तव में इसे कभी भी एक मुद्दा नहीं माना है क्योंकि मैं आमतौर पर अपने पुन: प्रयोज्य घटकों पर स्थिर तरीकों (आदि) में संभावित थ्रेडिंग बैडनेस के खिलाफ इस तरह की रक्षा करता हूं, और मैं स्थैतिक घटनाओं को नहीं करता हूं।

क्या मैं गलत कर रहा हूँ?


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

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

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

0

निर्माण पर अपने सभी घटनाओं को तार करें और उन्हें अकेला छोड़ दें। डेलिगेट वर्ग का डिज़ाइन संभवतः किसी अन्य उपयोग को सही ढंग से संभाल नहीं सकता है, जैसा कि मैं इस पोस्ट के अंतिम पैराग्राफ में बताऊंगा।

सबसे पहले, किसी इवेंट नोटिफिकेशन को इंटरसेप्ट करने की कोशिश करने का कोई मतलब नहीं है जब आपके ईवेंट हैंडलर्स को पहले से ही नोटिफिकेशन का जवाब देना है कि क्या / कैसे करना है ।

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

केवल एक हैंडलर को यह सूचित नहीं किया जाना चाहिए कि कोई घटना घटित हुई है, यदि वास्तव में घटना नहीं हुई है! इसलिए यदि आप एक हैंडलर को सूचित नहीं करना चाहते हैं, तो घटनाओं को उत्पन्न करना बंद करें (यानी नियंत्रण को निष्क्रिय करें या जो भी घटना का पता लगाने और पहली जगह में अस्तित्व में लाने के लिए जिम्मेदार है)।

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


[१] कोई भी उपयोगी ईवेंट हैंडलर नहीं है जो "समय में एक ही पल" पर होता है। सभी परिचालनों में एक समय-सीमा होती है। किसी भी एकल हैंडलर के पास प्रदर्शन करने के लिए चरणों का एक गैर-तुच्छ अनुक्रम हो सकता है। हैंडलर की सूची का समर्थन करने से कुछ भी नहीं बदलता है।
डैनियल ईयरविकर

[२] किसी घटना को अंजाम देते समय ताला लगाना कुल पागलपन है। यह अनिवार्य रूप से गतिरोध की ओर जाता है। स्रोत लॉक ए को निकालता है, घटना को फायर करता है, सिंक लॉक बी को बाहर निकालता है, अब दो लॉक आयोजित किए जाते हैं। क्या होगा यदि दूसरे थ्रेड में कुछ ऑपरेशन के परिणामस्वरूप ताले को रिवर्स ऑर्डर में निकाला जा सकता है? जब अलग-अलग डिज़ाइन किए गए / परीक्षण किए गए घटकों (जो घटनाओं का पूरा बिंदु है) के बीच लॉकिंग की ज़िम्मेदारी को विभाजित करने पर इस तरह के घातक संयोजनों को कैसे खारिज किया जा सकता है?
डैनियल ईयरविकेर

[३] इनमें से कोई भी मुद्दा किसी भी तरह से सामान्य मल्टीकास्ट प्रतिनिधियों / घटकों की एकल-पिरोया गई संरचना में घटनाओं की सभी व्यापक उपयोगिता को कम नहीं करता है, विशेष रूप से जीयूआई रूपरेखाओं में। यह उपयोग मामला घटनाओं के विशाल उपयोग को कवर करता है। एक मुक्त-थ्रेडेड तरीके से घटनाओं का उपयोग करना संदिग्ध मूल्य का है; यह किसी भी तरह से उनके डिजाइन या संदर्भों में उनकी स्पष्ट उपयोगिता को अमान्य नहीं करता है जहां वे समझ में आते हैं।
डैनियल ईयरविकेर

[४] थ्रेड्स + सिंक्रोनस इवेंट अनिवार्य रूप से एक लाल हेरिंग है। कतारबद्ध अतुल्यकालिक संचार जाने का मार्ग है।
डैनियल ईयरविकर

[१] मैं मापा समय का जिक्र नहीं कर रहा था ... मैं परमाणु परिचालनों के बारे में बात कर रहा था, जो तार्किक रूप से तुरंत घटित होते हैं ... और मेरा मतलब है कि जिन संसाधनों का उपयोग कर रहे हैं, उनमें से कुछ भी शामिल नहीं है, जबकि घटना हो रही है क्योंकि यह एक ताला के साथ क्रमबद्ध है।
त्रिनको

0

कृपया यहाँ देखें: http://www.danielfortunov.com/software/%24daniel_fortunovs_adventures_in_software_development/2009/04/23/net_event_invocation_thread_nafety यह सही समाधान है और इसका उपयोग हमेशा अन्य सभी वर्कअराउंड के बजाय किया जाना चाहिए।

"आप यह सुनिश्चित कर सकते हैं कि आंतरिक मंगलाचरण सूची में हमेशा कम से कम एक सदस्य होता है, जो इसे कुछ भी नहीं अनाम विधि के साथ शुरू करता है। क्योंकि किसी भी बाहरी पार्टी में अनाम विधि का संदर्भ नहीं हो सकता है, कोई भी बाहरी पार्टी विधि को हटा नहीं सकती है, इसलिए प्रतिनिधि कभी भी अशक्त नहीं होगा ”- जुएल लोवी द्वारा प्रोग्रामिंग .NET घटक, 2 संस्करण

public static event EventHandler<EventArgs> PreInitializedEvent = delegate { };  

public static void OnPreInitializedEvent(EventArgs e)  
{  
    // No check required - event will never be null because  
    // we have subscribed an empty anonymous delegate which  
    // can never be unsubscribed. (But causes some overhead.)  
    PreInitializedEvent(null, e);  
}  

0

मैं नहीं मानता कि प्रश्न सी # "ईवेंट" प्रकार के लिए विवश है। उस प्रतिबंध को हटाते हुए, पहिया को फिर से क्यों नहीं आविष्कार किया जाए और इन पंक्तियों के साथ कुछ करें?

घटना सूत्र को सुरक्षित रूप से उठाएं - सबसे अच्छा अभ्यास

  • बढ़ाने के दौरान किसी भी धागे से उप / सदस्यता समाप्त करने की क्षमता (दौड़ की शर्त हटा दी गई)
  • संचालक वर्ग स्तर पर + = और - = के लिए ओवरलोड करता है।
  • जेनेरिक कॉलर परिभाषित प्रतिनिधि

0

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

यहां मुख्य बिंदु यह है कि मंगलाचरण सूची को संशोधित किया जा सकता है यहां तक ​​कि घटना को उठाया जाता है।

/// <summary>
/// Thread safe event invoker
/// </summary>
public sealed class ThreadSafeEventInvoker
{
    /// <summary>
    /// Dictionary of delegates
    /// </summary>
    readonly ConcurrentDictionary<Delegate, DelegateHolder> delegates = new ConcurrentDictionary<Delegate, DelegateHolder>();

    /// <summary>
    /// List of delegates to be called, we need it because it is relatevely easy to implement a loop with list
    /// modification inside of it
    /// </summary>
    readonly LinkedList<DelegateHolder> delegatesList = new LinkedList<DelegateHolder>();

    /// <summary>
    /// locker for delegates list
    /// </summary>
    private readonly ReaderWriterLockSlim listLocker = new ReaderWriterLockSlim();

    /// <summary>
    /// Add delegate to list
    /// </summary>
    /// <param name="value"></param>
    public void Add(Delegate value)
    {
        var holder = new DelegateHolder(value);
        if (!delegates.TryAdd(value, holder)) return;

        listLocker.EnterWriteLock();
        delegatesList.AddLast(holder);
        listLocker.ExitWriteLock();
    }

    /// <summary>
    /// Remove delegate from list
    /// </summary>
    /// <param name="value"></param>
    public void Remove(Delegate value)
    {
        DelegateHolder holder;
        if (!delegates.TryRemove(value, out holder)) return;

        Monitor.Enter(holder);
        holder.IsDeleted = true;
        Monitor.Exit(holder);
    }

    /// <summary>
    /// Raise an event
    /// </summary>
    /// <param name="args"></param>
    public void Raise(params object[] args)
    {
        DelegateHolder holder = null;

        try
        {
            // get root element
            listLocker.EnterReadLock();
            var cursor = delegatesList.First;
            listLocker.ExitReadLock();

            while (cursor != null)
            {
                // get its value and a next node
                listLocker.EnterReadLock();
                holder = cursor.Value;
                var next = cursor.Next;
                listLocker.ExitReadLock();

                // lock holder and invoke if it is not removed
                Monitor.Enter(holder);
                if (!holder.IsDeleted)
                    holder.Action.DynamicInvoke(args);
                else if (!holder.IsDeletedFromList)
                {
                    listLocker.EnterWriteLock();
                    delegatesList.Remove(cursor);
                    holder.IsDeletedFromList = true;
                    listLocker.ExitWriteLock();
                }
                Monitor.Exit(holder);

                cursor = next;
            }
        }
        catch
        {
            // clean up
            if (listLocker.IsReadLockHeld)
                listLocker.ExitReadLock();
            if (listLocker.IsWriteLockHeld)
                listLocker.ExitWriteLock();
            if (holder != null && Monitor.IsEntered(holder))
                Monitor.Exit(holder);

            throw;
        }
    }

    /// <summary>
    /// helper class
    /// </summary>
    class DelegateHolder
    {
        /// <summary>
        /// delegate to call
        /// </summary>
        public Delegate Action { get; private set; }

        /// <summary>
        /// flag shows if this delegate removed from list of calls
        /// </summary>
        public bool IsDeleted { get; set; }

        /// <summary>
        /// flag shows if this instance was removed from all lists
        /// </summary>
        public bool IsDeletedFromList { get; set; }

        /// <summary>
        /// Constuctor
        /// </summary>
        /// <param name="d"></param>
        public DelegateHolder(Delegate d)
        {
            Action = d;
        }
    }
}

और उपयोग है:

    private readonly ThreadSafeEventInvoker someEventWrapper = new ThreadSafeEventInvoker();
    public event Action SomeEvent
    {
        add { someEventWrapper.Add(value); }
        remove { someEventWrapper.Remove(value); }
    }

    public void RaiseSomeEvent()
    {
        someEventWrapper.Raise();
    }

परीक्षा

मैंने निम्नलिखित तरीके से इसका परीक्षण किया। मेरे पास एक धागा है जो इस तरह से वस्तुओं को बनाता और नष्ट करता है:

var objects = Enumerable.Range(0, 1000).Select(x => new Bar(foo)).ToList();
Thread.Sleep(10);
objects.ForEach(x => x.Dispose());

एक Bar(एक श्रोता वस्तु) कंस्ट्रक्टर में मैं सदस्यता लेता हूं SomeEvent(जिसे ऊपर दिखाया गया है) और सदस्यता समाप्त Dispose:

    public Bar(Foo foo)
    {
        this.foo = foo;
        foo.SomeEvent += Handler;
    }

    public void Handler()
    {
        if (disposed)
            Console.WriteLine("Handler is called after object was disposed!");
    }

    public void Dispose()
    {
        foo.SomeEvent -= Handler;
        disposed = true;
    }

इसके अलावा मेरे पास कुछ धागे हैं जो एक लूप में घटना को बढ़ाते हैं।

इन सभी कार्यों को एक साथ किया जाता है: कई श्रोताओं को बनाया जाता है और नष्ट कर दिया जाता है और एक ही समय में घटना को निकाल दिया जाता है।

यदि कोई दौड़ की स्थिति थी, तो मुझे एक संदेश को कंसोल में देखना चाहिए, लेकिन यह खाली है। लेकिन अगर मैं सामान्य रूप से clr घटनाओं का उपयोग करता हूं तो मैं इसे चेतावनी संदेशों से भरा हुआ देखता हूं। इसलिए, मैं यह निष्कर्ष निकाल सकता हूं कि c # में थ्रेड सुरक्षित घटनाओं को लागू करना संभव है।

तुम क्या सोचते हो?


मुझे काफी अच्छा लग रहा है। हालांकि मुझे लगता है कि आपके परीक्षण के आवेदन disposed = trueसे पहले ऐसा होना संभव है (सिद्धांत रूप में) foo.SomeEvent -= Handler, एक गलत सकारात्मक उत्पादन। लेकिन इसके अलावा, कुछ चीजें हैं जिन्हें आप बदलना चाहते हैं। आप वास्तव try ... finallyमें तालों के लिए उपयोग करना चाहते हैं - यह आपको न केवल थ्रेड-सुरक्षित बनाने में मदद करेगा, बल्कि गर्भपात-सुरक्षित भी होगा। इस बात का उल्लेख नहीं है कि आप उस मूर्खता से छुटकारा पा सकते हैं try catch। और आप Add/ Remove- में पारित प्रतिनिधि की जाँच नहीं कर रहे हैं - यह हो सकता है null(आपको सीधे Add/ में फेंक देना चाहिए Remove)।
लुआं
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.