c# - सी#घटनाक्रम और थ्रेड सुरक्षा




multithreading events (10)

अद्यतन करें

सी # 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 में किया जाना चाहिए था?


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

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

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

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

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

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

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

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

अधिक जानकारी और स्रोत कोड के लिए, इस ब्लॉग पोस्ट पर .NET ईवेंट आमंत्रण थ्रेड सुरक्षा पर जाएं जो मैंने इस प्रश्न से पहले एक दिन पहले प्रकाशित किया था (!)

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


एकल थ्रेडेड आवेदकों के लिए, आप correc हैं यह कोई मुद्दा नहीं है।

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

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

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

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


निर्माण पर अपने सभी कार्यक्रमों को तार दें और उन्हें अकेला छोड़ दें। प्रतिनिधि वर्ग का डिज़ाइन संभावित रूप से किसी अन्य उपयोग को संभाल नहीं सकता है, क्योंकि मैं इस पोस्ट के अंतिम अनुच्छेद में समझाऊंगा।

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

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

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

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


मुझे लगता है कि बहुत से लोग इसे करने की विस्तार विधि की ओर जा रहे हैं ...

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() );

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


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

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

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

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

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 - रेस कंडीशन इश्यू ) मैंने एरिक के ब्लॉग पोस्ट को पढ़ा, मैं मानता हूं कि एच (हैंडलर) को इसे खुद को खराब करते समय संभालना चाहिए, लेकिन घटना को अपरिवर्तनीय / धागा सुरक्षित नहीं बनाया जा सकता है? आईई, अपनी रचना पर लॉक फ्लैग सेट करें, ताकि जब भी इसे कहा जा सके, तो यह निष्पादित होने पर सभी सब्सक्राइब करने और इसे सब्सक्राइब करने में ताला लगा देता है?

निष्कर्ष ,

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


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

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



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

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

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

TheEvent(this, EventArgs.Empty);

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

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


I don't believe the question is constrained to the c# "event" type. Removing that restriction, why not re-invent the wheel a bit and do something along these lines?

Raise event thread safely - best practice

  • Ability to sub/unsubscribe from any thread while within a raise (race condition removed)
  • Operator overloads for += and -= at the class level.
  • Generic caller-defined delegate

Please take a look here: http://www.danielfortunov.com/software/%24daniel_fortunovs_adventures_in_software_development/2009/04/23/net_event_invocation_thread_safety This is the correct solution and should always be used instead of all other workarounds.

“You can ensure that the internal invocation list always has at least one member by initializing it with a do-nothing anonymous method. Because no external party can have a reference to the anonymous method, no external party can remove the method, so the delegate will never be null” — Programming .NET Components, 2nd Edition, by Juval Löwy

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);  
}  




events