c# - संग्रह संशोधित किया गया था; गणना ऑपरेशन निष्पादित नहीं हो सकता है




wcf concurrency (8)

आप सब्सक्राइबर डिक्शनरी ऑब्जेक्ट को एक ही प्रकार के अस्थायी शब्दकोश ऑब्जेक्ट पर कॉपी कर सकते हैं और फिर फ़ोरैच लूप का उपयोग करके अस्थायी शब्दकोश ऑब्जेक्ट को फिर से चालू कर सकते हैं।

मैं इस त्रुटि के नीचे नहीं जा सकता, क्योंकि जब डीबगर संलग्न होता है, ऐसा प्रतीत नहीं होता है। नीचे कोड है।

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

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

क्या आपको इस कोड के साथ कोई समस्या दिखाई दे रही है? क्या मुझे शब्दकोश थ्रेड-सुरक्षित बनाने की ज़रूरत है?

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class SubscriptionServer : ISubscriptionServer
{
    private static IDictionary<Guid, Subscriber> subscribers;

    public SubscriptionServer()
    {            
        subscribers = new Dictionary<Guid, Subscriber>();
    }

    public void NotifySubscribers(DataRecord sr)
    {
        foreach(Subscriber s in subscribers.Values)
        {
            try
            {
                s.Callback.SignalData(sr);
            }
            catch (Exception e)
            {
                DCS.WriteToApplicationLog(e.Message, 
                  System.Diagnostics.EventLogEntryType.Error);

                UnsubscribeEvent(s.ClientId);
            }
        }
    }


    public Guid SubscribeEvent(string clientDescription)
    {
        Subscriber subscriber = new Subscriber();
        subscriber.Callback = OperationContext.Current.
                GetCallbackChannel<IDCSCallback>();

        subscribers.Add(subscriber.ClientId, subscriber);

        return subscriber.ClientId;
    }


    public void UnsubscribeEvent(Guid clientId)
    {
        try
        {
            subscribers.Remove(clientId);
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                    e.Message);
        }
    }
}

जब कोई ग्राहक सदस्यता समाप्त करता है तो आप गणना के दौरान सब्सक्राइबर्स के संग्रह की सामग्री बदल रहे हैं।

इसे ठीक करने के कई तरीके हैं, एक लूप के लिए एक स्पष्ट का उपयोग करने के लिए बदल रहा है .ToList() :

public void NotifySubscribers(DataRecord sr)  
{
    foreach(Subscriber s in subscribers.Values.ToList())
    {
                                              ^^^^^^^^^  
        ...

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


दरअसल समस्या यह है कि आप सूची से तत्वों को हटा रहे हैं और सूची को पढ़ना जारी रखने की उम्मीद करते हैं जैसे कुछ भी नहीं हुआ था।

आपको वास्तव में क्या करना है, शुरुआत से और शुरुआत में शुरू करना है। भले ही आप सूची से तत्व हटा दें, आप इसे पढ़ना जारी रख सकेंगे।


मेरे पास एक ही समस्या थी, और जब मैंने foreach बजाय लूप का उपयोग किया था तो इसे हल किया गया था।

// foreach (var item in itemsToBeLast)
for (int i = 0; i < itemsToBeLast.Count; i++)
{
    var matchingItem = itemsToBeLast.FirstOrDefault(item => item.Detach);

   if (matchingItem != null)
   {
      itemsToBeLast.Remove(matchingItem);
      continue;
   }
   allItems.Add(itemsToBeLast[i]);// (attachDetachItem);
}

मैंने इसके लिए कई विकल्प देखे हैं लेकिन मेरे लिए यह सबसे अच्छा था।

ListItemCollection collection = new ListItemCollection();
        foreach (ListItem item in ListBox1.Items)
        {
            if (item.Selected)
                collection.Add(item);
        }

फिर संग्रह के माध्यम से बस लूप।

ध्यान रखें कि ListItemCollection में डुप्लिकेट हो सकते हैं। डिफ़ॉल्ट रूप से संग्रह में डुप्लिकेट जोड़े जाने से कुछ भी नहीं है। डुप्लिकेट से बचने के लिए आप यह कर सकते हैं:

ListItemCollection collection = new ListItemCollection();
            foreach (ListItem item in ListBox1.Items)
            {
                if (item.Selected && !collection.Contains(item))
                    collection.Add(item);
            }

अवैधऑपरेशन अपवाद - एक अवैधऑपरेशन अपवाद हुआ है। यह एक foreach-loop में "संग्रह संशोधित किया गया" की रिपोर्ट करता है

ऑब्जेक्ट हटा दिए जाने के बाद, ब्रेक स्टेटमेंट का उपयोग करें।

उदाहरण के लिए:

ArrayList list = new ArrayList(); 

foreach (var item in list)
{
    if(condition)
    {
        list.remove(item);
        break;
    }
}

नोट : सामान्य रूप से .NET संग्रह एक ही समय में गणना और संशोधित होने का समर्थन नहीं करते हैं। यदि आप गणना सूची के बीच में संग्रह सूची को संशोधित करने का प्रयास करते हैं तो यह एक अपवाद उठाएगा।

तो इस त्रुटि के पीछे समस्या यह है कि हम सूची / शब्दकोश को संशोधित नहीं कर सकते हैं, जबकि हम लूपिंग कर रहे हैं। लेकिन अगर हम अपनी चाबियों की अस्थायी सूची का उपयोग करके एक शब्दकोश को पुन: सक्रिय करते हैं, तो समानांतर में हम शब्दकोश वस्तु को संशोधित कर सकते हैं, क्योंकि अब हम शब्दकोश को पुन: सक्रिय नहीं कर रहे हैं (और इसके मुख्य संग्रह को पुन: सक्रिय कर रहे हैं)।

नमूना:

//get key collection from dictionary into a list to loop through
List<int> keys = new List<int>(Dictionary.Keys);

// iterating key collection using simple for-each loop
foreach (int key in keys)
{
  // Now we can perform any modification with values of dictionary.
  Dictionary[key] = Dictionary[key] - 1;
}

इस समाधान के बारे में यहां एक ब्लॉग पोस्ट है

और स्टैक ओवरफ्लो में गहरे गोता लगाने के लिए: यह त्रुटि क्यों होती है?





thread-safety