2009-12-07 15 views
6

मेरे पास एक बहुप्रचारित ऐप में एक अजीब बग है:क्या .NET में एक पठनीय क्षेत्र शून्य हो सकता है?

public class MyClass 
{ 
    private readonly Hashtable HashPrefs; 
    public MyClass(int id) 
    { 
    HashPrefs = new Hashtable(); 
    } 

    public void SomeMethodCalledFromAnotherThread(string hashKey,string hashValue) 
{ 
    if (HashPrefs.Contains(hashKey)) // <-- throws NullReferenceException 
    { 

    } 
} 
} 

एक थ्रेड करता है:

 SomeQueue.Add(new MyClass(1)); 

और दूसरा धागा करता है:

 SomeQueue.Dequeue().SomeMethodCalledFromAnotherThread(SomeClass.SomeMethod(),"const value"); 

लेकिन दूसरा थ्रेड कॉल कैसे हो सकता है कन्स्ट्रक्टर खत्म होने से पहले विधि खत्म हो गई है?

संपादित करें: मैंने फ़ंक्शन पैरामीटर के साथ भाग जोड़ा, क्योंकि ऐसा लगता है कि यह प्रासंगिक हो सकता है। जहां तक ​​मैं कह सकता हूं, हैशकी को पारित किया जा सकता है, क्योंकि कुछ विधि() हमेशा एक प्रासंगिक स्ट्रिंग देता है।

जैसा कि अन्य ने इंगित किया है, अगर समस्या एक शून्य हैकके पैरामीटर शामिल है() में, अपवाद ArgumentNullException होगा।

+3

मल्टी-थ्रेडिंग नरक में आपका स्वागत है ... –

+1

ओह, मैं कुछ सालों से बहु-थ्रेडिंग कर रहा हूं :-(। यह केवल मेरी अनोखी त्रुटियों में से एक है जिसमें मेरा सिर कताई था। – Radu094

+0

आपका इंस्टेंस वैरिएबल थ्रेड-विशिष्ट कैश से पढ़ा जा सकता है। मेरा संपादित उत्तर देखें। –

उत्तर

11

प्रतिबिंब ऐसा करने का एक तरीका है (SetField)।

इस जिज्ञासा प्राप्त करने के लिए एक दूसरा तरीका अगर आप बहुत जल्दी संदर्भ दे देना, .ctor से बाहर this गुजर (या sometihng इस तरह के एक क्षेत्र एक गुमनाम विधि/लैम्ब्डा में ले लिया था क्योंकि एक अंतर्निहित this, शामिल) के द्वारा होता है:

public MyClass(int id) 
{ 
    Program.Test(this); // oopsie ;-p 
    HashPrefs = new Hashtable(); 
} 

या अधिक होने की संभावना (प्रश्न दिए गए):

SomeQueue.Add(this); 

एक और प्रासंगिक सवाल किया जा सकता है - यह पहली जगह में नहीं सौंपा जा सकता है? और इसका उत्तर हाँ है, खासकर अगर आप धारावाहिकरण का उपयोग कर रहे हैं। लोकप्रिय धारणा के विपरीत, आप वास्तव में रचनाकारों को बाईपास कर सकते हैं, यदि आपके पास कोई कारण है; DataContractSerializer कुछ है कि यह करता है का एक अच्छा उदाहरण है ...

निम्नलिखित एक अशक्त क्षेत्र के साथ एक MyClass पैदा करेगा:

MyClass obj = (MyClass) 
    System.Runtime.Serialization.FormatterServices.GetUninitializedObject(
     typeof(MyClass)); 
+0

पर शून्य हो रहा है, धारावाहिकता के साथ अच्छा बिंदु। जिज्ञासा से बाहर: यदि ऑब्जेक्ट को क्रमबद्ध किया जाता है तो हैशटेबल वहां होता है, तो deserializaton एक ऑब्जेक्ट नहीं बनायेगा जगह में सही हैशटेबल? – Radu094

+0

दिए गए उदाहरण में, शायद यह होगा (यह मानते हुए कि धारावाहिक होने पर क्षेत्र अस्तित्व में था, और इसका नाम बदल नहीं लिया गया है)। यदि आप कस्टम के साथ कुछ मजेदार चीजें कर रहे हैं serializers, शायद नहीं। मैं स्वीकार करूंगा कि ये किनारे के मामले हैं, लेकिन हे! यह एक पागल दुनिया है। 'GetUninitializedObject' दृष्टिकोण निश्चित रूप से "जैसा है" काम करेगा। –

+0

डब्ल्यूसीएफ ऑब्जेक्ट deserialization इस फैशन में वस्तुओं बनाता है। इस के आसपास काम करने के लिए, मैंने इन पाठक क्षेत्रों को केवल पढ़ने योग्य गुणों में बनाया है जो अंतर्निहित क्षेत्र को पहली पहुंच पर प्रारंभ करते हैं। –

7

हां, केवल पढ़ने के क्षेत्र प्रतिबिंब के माध्यम से पहुंचा जा सकता है और उनके मूल्य बदल गए हैं। तो आपके प्रश्न का उत्तर हाँ है, यह संभव है।

हालांकि कई अन्य चीजें हैं जो आपके लिए भी समस्याएं पैदा कर सकती हैं। मल्टी-थ्रेडेड कोड लिखना मुश्किल है और उत्पन्न होने वाली समस्याओं का निदान करना मुश्किल है।


एक तरफ ध्यान दें के रूप में, आप यह सुनिश्चित करें कि आप एक ArgumentNullException नहीं मिल रहा है कर रहे हैं? यदि somekeynull है, Hashtable.Contains विधि ArgumentNullException फेंक देगी। शायद यह मुद्दा है?

+0

एक माँ के लिए टी वहाँ मैं एसओओ विश्वास करना चाहता था कि यह एक ArgumentNullException था। महान कॉल, लेकिन नहीं! यह 'एक नल रेफरेंस :-( – Radu094

1

क्या आप सुनिश्चित हैं कि यह हैशप्रफ्स। क्या यह अपवाद फेंक रहा है, और जो भी "कुछकी" का प्रतिनिधित्व नहीं करता है? यदि आप नहीं हैं, तो क्या आप कृपया पूर्ण अपवाद विवरण पोस्ट कर सकते हैं, जैसा कि इसके ToString विधि द्वारा लौटाया गया है?

3

सबसे पहले, अन्य धागा कन्स्ट्रक्टर पूरा होने तक इंस्टेंस तक नहीं पहुंच सकता है, क्योंकि उदाहरण स्वयं को कन्स्ट्रक्टर समाप्त होने तक असाइन नहीं किया जाएगा। ऐसा कहा जा रहा है कि, अन्य धागा परिवर्तनीय तक पहुंच सकता है जो कि कन्स्ट्रक्टर पूरा होने से पहले उदाहरण रखता है, लेकिन उस समय null होगा। इससे NullReferenceException उत्पन्न होगा, लेकिन यह उदाहरण विधि से उदाहरण के उदाहरण से नहीं, उदाहरण तक पहुंचने वाली विधि से आएगा।

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

एक MyClass के लिए एक संदर्भ युक्त और अन्य धागे के निर्माता के रूप निम्नलिखित वर्ग पर विचार करें:

public class MasterClass 
{ 
    private MyClass myClass; 
    private object syncRoot = new object(); // this is what we'll use to synchronize the code 

    public void Thread1Proc() 
    { 
     lock(syncRoot) 
     { 
      myClass = new MyClass(); 
     } 
    } 

    public void Thread2Proc() 
    { 
     lock(syncRoot) 
     { 
      myClas.SomeMethodCalledFromAnotherThread(); 
     } 
    } 
} 

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

संपादित

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

1

बस

HashPrefs = Hashtable.Synchronized(new Hashtable()); 

पहिया बदलने की कोई जरूरत नहीं है।

+0

+1। मैं आम तौर पर पुराने गैर-जेनेरिक संग्रह प्रकारों का उपयोग नहीं करता हूं, लेकिन यह एक अच्छा खोज है! –

+1

उन्हें गलत रास्ते की ओर ले जाने की काफी संभावना है, क्योंकि मुझे दृढ़ता से संदेह है कि जांच के बाद '()' है, 'झूठी' के लिए शाखा एक तत्व जोड़ देगा। और निश्चित रूप से एक "सिंक्रनाइज़" 'हैशटेबल 'उन दो कॉलों को एकल इकाई के रूप में सिंक्रनाइज़ नहीं करेगा। –

+2

मुझे डर है कि समस्या सिंक्रनाइज़ेशन के साथ necesarly नहीं है कतार का, जितना ज्यादा मेरे हेस्टेबल के साथ कुछ बिंदु – Radu094

0

कई चीजें आप की कोशिश कर सकते हैं:

  1. आरंभ प्रयास करें एक सदस्य चर (कन्स्ट्रक्टर के बाहर) के रूप में हैशटेबल, और उसी नाम की सार्वजनिक संपत्ति के साथ, देखें कि कोई इसे असाइनमेंट करने के लिए कह रहा है, जैसे:

    public class MyClass { 
    private readonly Hashtable hashPrefs = new Hashtable(); 
    
    public MyClass(int id) 
    { 
    
    } 
    
    public Hashtable HashPrefs 
    { 
        set 
        { 
         throw new InvalidOperationException("This shouldn't happen"); 
        } 
    } 
    

    }

  2. दूसरे, किस प्रकार "SomeQueue" क्या है? क्या यह सिर्फ एक नियमित सूची <> है? या यह कुछ विशेष आत्म-कार्यान्वित कतार है जो कुछ एक्सएमएल/बाइनरी क्रमबद्धता पर निर्भर करती है? यदि हां, तो क्या आपने हैशफ्रफ़ को [सीरियलज़ेबल] के रूप में चिह्नित किया है? या एक [डेटामेम्बर] के रूप में?

1

क्या आप पूरी तरह से सुनिश्चित हैं कि HashPrefs.Contains एनपीई फेंक रहा है? मुझे लगता है कि SomeQueue.Dequeue().SomeMethodCalledFromAnotherThread() अधिक संभावित उम्मीदवार होगा ... क्योंकि संभावनाएं कन्स्ट्रक्टर हैं और ऑपरेशन को तब तक समाप्त नहीं किया गया है जब आप तत्व को डेक्यू करने का प्रयास करते हैं।

वास्तव में, किसी तत्व को हटाने से पहले SomeQueue.Count * जांचना बुरा विचार नहीं हो सकता है।

संपादित करें: या और भी बेहतर, अशक्त जांच SomeMethodCalledFromAnotherThread बुला, यह सोचते हैं कि अपने अन्य धागा एक पाश में कतार जाँच कर रहा है ... यह नहीं है क्योंकि इससे पहले, lock कतार में जोड़ने और विपंक्ति संचालन करने से पहले।

MyClass mine = SomeQueue.Dequeue(); 

if (null != mine) 
    mine.SomeMethodCalledFromAnotherThread(); 

* .NET 3.5 में एक विस्तार विधि जोड़ा गया।

+2

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

+0

उस स्थिति में, वह अस्वीकृत तत्व को स्टोर कर सकता था और उस पर एक विधि कॉल करने से पहले इसे शून्य के लिए जांच सकता था। – Powerlord

+0

यह बहुत अधिक समझ में आता है। संपादन के लिए ऊपर उठाया गया। –

0

एक जोड़े विचारों दिमाग में आते हैं:

  • hashtable समवर्ती एक असंगत आंतरिक स्थिति के लिए अग्रणी संशोधित किया जा रहा है।
  • कॉल करने के लिए कॉल समानता जांच करता है जो एक शून्य क्षेत्र वाले मूल्य को कम करने का प्रयास करता है। 'कुछकी' किस प्रकार का है?
+0

मैंने सवाल संपादित किया। कुछकी एक पैरामीटर – Radu094

3

आपके निर्देशों को फिर से व्यवस्थित किया जा सकता है ताकि MyClass के एक उदाहरण का संदर्भ कतार में पूरी तरह से बनाया गया हो। Here's इस विषय पर छूने वाले डबल-चेक किए गए लॉकिंग के बारे में एक लेख। लेख से:

वैन्स, CLR JIT टीम पर एक devlead स्पष्ट किया कि इस मुद्दे को CLR स्मृति मॉडल के आसपास है ... अनिवार्य रूप से स्मृति मॉडल नॉन-वोलाटाइल पढ़ता \ लिए अनुमति देता है कि परिवर्तन के रूप में रूप में लंबे समय पुनर्क्रमित जा करने के लिए लिखता है एक धागे के दृष्टिकोण से ध्यान नहीं दिया जा सकता है।

System.Threading.Thread.MemoryBarrier. पर एक नजर डालें आप इस तरह कुछ करने के लिए आवश्यकता हो सकती है:

MyClass temp = new MyClass(1); 
System.Threading.Thread.MemoryBarrier(); 
SomeQueue.Add(temp); 

MemoryBarrier सुनिश्चित करता है कि यह पहले सभी निर्देशों आगे बढ़ने से पहले क्रियान्वित कर रहे हैं।

+0

के रूप में पारित एक स्ट्रिंग है! मल्टीथ्रेडिंग की तरह लगता है टेबल पर वापस आ गया है। – Radu094

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