การสะสมถูกแก้ไข; การดำเนินการแจงนับอาจไม่ทำงาน


910

ฉันไม่สามารถไปที่ด้านล่างของข้อผิดพลาดนี้ได้เพราะเมื่อมีการแนบตัวดีบั๊กมันจะไม่เกิดขึ้น ด้านล่างเป็นรหัส

นี่คือเซิร์ฟเวอร์ WCF ในบริการ Windows เมธอด NotifySubscribeers ถูกเรียกโดยบริการเมื่อใดก็ตามที่มีเหตุการณ์ข้อมูล (ตามช่วงเวลาที่สุ่ม แต่ไม่บ่อยมาก - ประมาณ 800 ครั้งต่อวัน)

เมื่อไคลเอนต์ Windows Forms สมัครสมาชิก ID สมาชิกจะถูกเพิ่มไปยังพจนานุกรมสมาชิกและเมื่อลูกค้ายกเลิกสมาชิกมันจะถูกลบออกจากพจนานุกรม ข้อผิดพลาดเกิดขึ้นเมื่อ (หรือหลังจาก) ลูกค้ายกเลิกการเป็นสมาชิก ปรากฏว่าในครั้งต่อไปที่เรียกใช้เมธอด 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);
        }
    }
}

ในกรณีของฉันมันเป็นหลักประกันผลเพราะฉันใช้ไม่กี่รวม ("ตาราง") ที่ได้รับการแก้ไขในระหว่างกระบวนการ - ไม่ชัดเจนมากเมื่ออ่านรหัส อย่างไรก็ตามฉันโชคดีที่ผู้ใช้เหล่านั้นไม่จำเป็นต้องใช้ (ใช่! รหัสเก่าที่ไม่มีการระบายน้ำ) และฉันแก้ไขปัญหาของฉันโดยเพียงแค่ลบออก
Adi

คำตอบ:


1631

สิ่งที่น่าจะเกิดขึ้นคือ SignalData กำลังเปลี่ยนพจนานุกรมสมาชิกแบบทางอ้อมในระหว่างการวนรอบและนำไปสู่ข้อความนั้น คุณสามารถตรวจสอบได้โดยการเปลี่ยน

foreach(Subscriber s in subscribers.Values)

ถึง

foreach(Subscriber s in subscribers.Values.ToList())

ถ้าฉันพูดถูกปัญหาจะหายไป

โทรsubscribers.Values.ToList()สำเนาค่าของไปยังรายการแยกต่างหากในช่วงเริ่มต้นของsubscribers.Values foreachไม่มีสิ่งใดที่เข้าถึงรายการนี้ (ไม่มีชื่อตัวแปร!) ดังนั้นจึงไม่มีสิ่งใดสามารถแก้ไขได้ภายในลูป


14
BTW .ToList () มีอยู่ใน System.Core dll ซึ่งเข้ากันไม่ได้กับแอปพลิเคชัน. NET 2.0 ดังนั้นคุณอาจต้องเปลี่ยนแอปเป้าหมายเป็น. Net 3.5
mishal153

60
ฉันไม่เข้าใจว่าทำไมคุณถึงทำ ToList และสาเหตุที่แก้ไขทุกอย่าง
PositiveGuy

204
@CoffeeAddict: ปัญหาsubscribers.Valuesคือการแก้ไขภายในforeachลูป โทรsubscribers.Values.ToList()สำเนาค่าของไปยังรายการแยกต่างหากในช่วงเริ่มต้นของsubscribers.Values foreachไม่มีสิ่งใดที่เข้าถึงรายการนี้(ไม่มีชื่อตัวแปร!)ดังนั้นจึงไม่มีสิ่งใดสามารถแก้ไขได้ภายในลูป
BlueRaja - Danny Pflughoeft

31
โปรดทราบว่าToListอาจเกิดขึ้นได้หากคอลเลกชันถูกแก้ไขในขณะที่ToListกำลังดำเนินการอยู่
Sriram Sakthivel

16
ฉันค่อนข้างมั่นใจว่าคิดว่าไม่สามารถแก้ไขปัญหาได้ แต่ทำให้ยากที่จะทำซ้ำ ToListไม่ใช่การดำเนินการปรมาณู สิ่งที่น่าสนุกกว่านั้นคือToListทำเองforeachภายในเพื่อคัดลอกรายการลงในอินสแตนซ์รายการใหม่ซึ่งหมายความว่าคุณได้แก้ไขforeachปัญหาด้วยการเพิ่มการforeachทำซ้ำเพิ่มเติม (แม้ว่าจะเร็วกว่า)
Groo

113

เมื่อสมาชิกยกเลิกสมาชิกคุณกำลังเปลี่ยนเนื้อหาของคอลเลกชันของสมาชิกในระหว่างการแจงนับ

มีหลายวิธีในการแก้ไขปัญหานี้หนึ่งกำลังเปลี่ยน for วนรอบเพื่อใช้ชัดเจน.ToList():

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

64

วิธีที่มีประสิทธิภาพมากขึ้นในความคิดของฉันคือการมีรายการอื่นที่คุณประกาศว่าคุณใส่สิ่งที่ "จะถูกลบ" ใน จากนั้นหลังจากที่คุณเสร็จสิ้นการวนรอบหลักของคุณ (ไม่มี. ToList ()) คุณทำวนซ้ำอีกครั้งในรายการ "จะถูกลบ" ลบแต่ละรายการตามที่มันเกิดขึ้น ดังนั้นในชั้นเรียนของคุณคุณเพิ่ม:

private List<Guid> toBeRemoved = new List<Guid>();

จากนั้นคุณเปลี่ยนเป็น:

public void NotifySubscribers(DataRecord sr)
{
    toBeRemoved.Clear();

    ...your unchanged code skipped...

   foreach ( Guid clientId in toBeRemoved )
   {
        try
        {
            subscribers.Remove(clientId);
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                e.Message);
        }
   }
}

...your unchanged code skipped...

public void UnsubscribeEvent(Guid clientId)
{
    toBeRemoved.Add( clientId );
}

สิ่งนี้จะไม่เพียง แต่แก้ปัญหาของคุณเท่านั้น แต่ยังป้องกันไม่ให้คุณต้องสร้างรายการจากพจนานุกรมของคุณซึ่งมีราคาแพงหากมีสมาชิกจำนวนมากอยู่ในนั้น สมมติว่ารายชื่อสมาชิกที่จะลบในการทำซ้ำใด ๆ ที่กำหนดนั้นต่ำกว่าจำนวนทั้งหมดในรายการนี้ควรจะเร็วกว่า แต่แน่นอนว่าคุณสามารถโปรไฟล์เพื่อให้แน่ใจว่าเป็นกรณีนี้หากมีข้อสงสัยในสถานการณ์การใช้งานเฉพาะของคุณ


9
ฉันคาดว่าสิ่งนี้จะคุ้มค่าหากคุณมีคุณกำลังทำงานกับคอลเล็กชันขนาดใหญ่ ถ้ามันเล็กฉันก็แค่ ToList และไปต่อ
Karl Kieninger

42

คุณยังสามารถล็อคพจนานุกรมสมาชิกของคุณเพื่อป้องกันไม่ให้ถูกแก้ไขเมื่อใดก็ตามที่ถูกวนซ้ำ:

 lock (subscribers)
 {
         foreach (var subscriber in subscribers)
         {
               //do something
         }
 }

2
นั่นเป็นตัวอย่างที่สมบูรณ์หรือไม่ ฉันมีคลาส (_dictionary obj ด้านล่าง) ที่มีพจนานุกรมทั่วไป <string, int> ชื่อ MarkerFrequencies แต่การทำเช่นนี้ไม่สามารถแก้ไขข้อผิดพลาดได้ทันที: การล็อก (_dictionary.MarkerFrequencies) {foreach (KeyValuePair <string, int> pair ใน _dictionary.MarkerFrequencies) {... }}
Jon Coombs

4
@JCoombs เป็นไปได้ว่าคุณกำลังแก้ไขอาจกำหนดMarkerFrequenciesพจนานุกรมภายในล็อคเองซึ่งหมายความว่าอินสแตนซ์ดั้งเดิมจะไม่ถูกล็อคอีกต่อไป นอกจากนี้ยังพยายามใช้forแทนการforeachดูนี้และนี้ แจ้งให้เราทราบหากแก้ไขได้ไหม
Mohammad Sepahvand

1
ในที่สุดฉันก็ได้แก้ไขข้อผิดพลาดนี้และเคล็ดลับเหล่านี้มีประโยชน์ - ขอบคุณ! ชุดข้อมูลของฉันมีขนาดเล็กและการดำเนินการนี้เป็นเพียงการอัปเดตการแสดงผล UI ดังนั้นการวนซ้ำสำเนาจะดีที่สุด (ฉันยังทดลองใช้แทน foreach หลังจากล็อก MarkerFrequencies ในทั้งสองเธรดซึ่งช่วยป้องกันความผิดพลาด แต่ดูเหมือนจะต้องการงานแก้ไขเพิ่มเติมและแนะนำความซับซ้อนมากขึ้นเหตุผลเดียวของฉันที่มีสองเธรดอยู่ที่นี่คือการอนุญาตให้ผู้ใช้ยกเลิก การดำเนินการโดยวิธีการที่ UI ไม่ได้แก้ไขข้อมูลนั้นโดยตรง)
Jon Coombs

9
ปัญหาคือสำหรับแอปพลิเคชันขนาดใหญ่การล็อกอาจเป็นผลการดำเนินงานที่สำคัญ - ดีกว่าการใช้คอลเลกชันในSystem.Collections.Concurrentเนมสเปซ
BrainSlugs83

28

ทำไมข้อผิดพลาดนี้?

โดยทั่วไปแล้วการรวบรวม. Net ไม่สนับสนุนการแจกแจงและแก้ไขในเวลาเดียวกัน ถ้าคุณพยายามที่จะแก้ไขรายการคอลเลกชันในระหว่างการแจงนับมันยกข้อยกเว้น ดังนั้นปัญหาที่อยู่เบื้องหลังข้อผิดพลาดนี้คือเราไม่สามารถแก้ไขรายการ / พจนานุกรมในขณะที่เราวนซ้ำกันได้

หนึ่งในโซลูชั่น

หากเราย้ำพจนานุกรมโดยใช้รายการของคีย์ในแบบคู่ขนานเราสามารถแก้ไขออบเจ็กต์พจนานุกรมได้เนื่องจากเราทำการวนซ้ำผ่านคอลเลคชั่นคีย์ไม่ใช่พจนานุกรม (และวนซ้ำการรวบรวมคีย์)

ตัวอย่าง

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

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

นี่คือโพสต์บล็อกเกี่ยวกับการแก้ปัญหานี้

และสำหรับการดำน้ำลึกใน StackOverflow: ทำไมข้อผิดพลาดนี้เกิดขึ้น?


ขอบคุณ! คำอธิบายที่ชัดเจนว่าทำไมมันเกิดขึ้นและทำให้ฉันมีวิธีแก้ไขในใบสมัครของฉันทันที
Kim Crosser

5

จริงๆแล้วปัญหาดูเหมือนว่าฉันจะลบองค์ประกอบออกจากรายการและคาดว่าจะอ่านรายการต่อไปราวกับว่าไม่มีอะไรเกิดขึ้น

สิ่งที่คุณต้องทำจริงๆคือเริ่มจากจุดสิ้นสุดและกลับไปที่จุดเริ่มต้น แม้ว่าคุณจะลบองค์ประกอบออกจากรายการคุณจะสามารถอ่านมันได้


ฉันไม่เห็นว่าจะสร้างความแตกต่างได้อย่างไร? หากคุณลบองค์ประกอบที่อยู่ตรงกลางของรายการจะยังคงมีข้อผิดพลาดอยู่เนื่องจากไม่สามารถค้นหารายการที่พยายามเข้าถึงได้หรือไม่
Zapnologica

4
@Zapnologica ความแตกต่างคือ - คุณจะไม่แจกแจงรายการ - แทนที่จะทำสำหรับ / แต่ละรายการคุณจะทำเพื่อ / ต่อไปและเข้าถึงด้วยจำนวนเต็ม - คุณสามารถปรับเปลี่ยนรายการ a ใน for / next loop แต่ไม่เคยอยู่ใน for / each loop (เพราะ for / แต่ละenumerates ) - คุณยังสามารถทำมันไปข้างหน้าใน for / next ได้หากคุณมีตรรกะพิเศษในการปรับเคาน์เตอร์ของคุณ ฯลฯ
BrainSlugs83

4

InvalidOperationException- เกิด InvalidOperationException รายงานว่า "คอลเลกชันถูกแก้ไข" ใน foreach-loop

ใช้คำสั่ง break เมื่อวัตถุถูกลบ

อดีต:

ArrayList list = new ArrayList(); 

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

วิธีแก้ปัญหาที่ดีและเรียบง่ายถ้าคุณรู้ว่ารายการของคุณมีรายการที่ต้องการลบออกมากที่สุด
Tawab Wakil

3

ฉันมีปัญหาเดียวกันและมันก็แก้ไขได้เมื่อฉันใช้ห่วงแทนforforeach

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

11
รหัสนี้ผิดและจะข้ามบางรายการในคอลเลกชันหากองค์ประกอบใด ๆ จะถูกลบออก ตัวอย่างเช่น: คุณมี var arr = ["a", "b", "c"] และในการวนซ้ำครั้งแรก (i = 0) คุณลบองค์ประกอบที่ตำแหน่ง 0 (องค์ประกอบ "a") หลังจากนั้นองค์ประกอบอาร์เรย์ทั้งหมดจะย้ายหนึ่งตำแหน่งขึ้นไปและอาร์เรย์จะเป็น ["b", "c"] ดังนั้นในการทำซ้ำครั้งถัดไป (i = 1) คุณจะตรวจสอบองค์ประกอบที่ตำแหน่ง 1 ซึ่งจะเป็น "c" ไม่ใช่ "b" นี่เป็นสิ่งที่ผิด ในการแก้ไขปัญหานั้นคุณต้องย้ายจากล่างขึ้นบน
Kaspars Ozols

3

โอเคสิ่งที่ช่วยฉันได้คือทำซ้ำย้อนหลัง ฉันพยายามลบรายการออกจากรายการ แต่วนขึ้นไปข้างบนและทำให้เมามากขึ้นเนื่องจากรายการไม่มีอยู่อีกต่อไป:

for (int x = myList.Count - 1; x > -1; x--)
                        {

                            myList.RemoveAt(x);

                        }

2

ฉันเห็นตัวเลือกมากมายสำหรับสิ่งนี้ แต่สำหรับฉันอันนี้ดีที่สุด

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

รหัสนี้จะป้องกันไม่ให้มีการป้อนข้อมูลซ้ำลงในฐานข้อมูลอย่างไร ฉันได้เขียนสิ่งที่คล้ายกันและเมื่อฉันเพิ่มผู้ใช้ใหม่จากกล่องรายการถ้าฉันตั้งใจเลือกหนึ่งที่อยู่ในรายการแล้วมันจะสร้างรายการที่ซ้ำกัน คุณมีข้อเสนอแนะใด ๆ @Mike หรือไม่?
Jamie

1

คำตอบที่ยอมรับนั้นไม่ชัดเจนและไม่ถูกต้องในกรณีที่เลวร้ายที่สุด หากมีการเปลี่ยนแปลงในระหว่างToList()นั้นคุณยังคงสามารถพบข้อผิดพลาดได้ นอกจากนี้lockควรพิจารณาถึงประสิทธิภาพและความปลอดภัยของเธรดหากคุณมีสมาชิกสาธารณะวิธีการแก้ไขที่เหมาะสมสามารถใช้ประเภทที่ไม่เปลี่ยนรูปชนิดที่ไม่เปลี่ยนรูป

โดยทั่วไปประเภทไม่เปลี่ยนรูปหมายความว่าคุณไม่สามารถเปลี่ยนสถานะของมันเมื่อสร้างขึ้น ดังนั้นรหัสของคุณควรมีลักษณะดังนี้:

public class SubscriptionServer : ISubscriptionServer
{
    private static ImmutableDictionary<Guid, Subscriber> subscribers = ImmutableDictionary<Guid, Subscriber>.Empty;
    public void SubscribeEvent(string id)
    {
        subscribers = subscribers.Add(Guid.NewGuid(), new Subscriber());
    }
    public void NotifyEvent()
    {
        foreach(var sub in subscribers.Values)
        {
            //.....This is always safe
        }
    }
    //.........
}

สิ่งนี้มีประโยชน์อย่างยิ่งหากคุณมีสมาชิกสาธารณะ คลาสอื่นสามารถอยู่foreachในประเภทที่ไม่เปลี่ยนรูปได้เสมอโดยไม่ต้องกังวลเกี่ยวกับคอลเลกชันที่ถูกแก้ไข


1

นี่คือสถานการณ์เฉพาะที่รับประกันวิธีการเฉพาะ:

  1. Dictionaryมีระบุบ่อย
  2. Dictionaryมีการปรับเปลี่ยนบ่อย

ในสถานการณ์นี้การสร้างสำเนาของDictionary(หรือDictionary.Values) ก่อนการแจงนับทุกครั้งอาจมีค่าใช้จ่ายค่อนข้างสูง ความคิดของฉันเกี่ยวกับการแก้ปัญหานี้คือการใช้ซ้ำสำเนาแคชเดียวกันใน enumerations หลายและชมIEnumeratorของเดิมDictionaryสำหรับข้อยกเว้น ตัวแจงนับจะถูกแคชพร้อมกับข้อมูลที่ถูกคัดลอกและสอบปากคำก่อนเริ่มการแจงนับใหม่ ในกรณีที่มีข้อยกเว้นสำเนาแคชจะถูกยกเลิกและจะถูกสร้างขึ้นใหม่ นี่คือการดำเนินการตามแนวคิดนี้ของฉัน:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

public class EnumerableSnapshot<T> : IEnumerable<T>, IDisposable
{
    private IEnumerable<T> _source;
    private IEnumerator<T> _enumerator;
    private ReadOnlyCollection<T> _cached;

    public EnumerableSnapshot(IEnumerable<T> source)
    {
        _source = source ?? throw new ArgumentNullException(nameof(source));
    }

    public IEnumerator<T> GetEnumerator()
    {
        if (_source == null) throw new ObjectDisposedException(this.GetType().Name);
        if (_enumerator == null)
        {
            _enumerator = _source.GetEnumerator();
            _cached = new ReadOnlyCollection<T>(_source.ToArray());
        }
        else
        {
            var modified = false;
            if (_source is ICollection collection) // C# 7 syntax
            {
                modified = _cached.Count != collection.Count;
            }
            if (!modified)
            {
                try
                {
                    _enumerator.MoveNext();
                }
                catch (InvalidOperationException)
                {
                    modified = true;
                }
            }
            if (modified)
            {
                _enumerator.Dispose();
                _enumerator = _source.GetEnumerator();
                _cached = new ReadOnlyCollection<T>(_source.ToArray());
            }
        }
        return _cached.GetEnumerator();
    }

    public void Dispose()
    {
        _enumerator?.Dispose();
        _enumerator = null;
        _cached = null;
        _source = null;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public static class EnumerableSnapshotExtensions
{
    public static EnumerableSnapshot<T> ToEnumerableSnapshot<T>(
        this IEnumerable<T> source) => new EnumerableSnapshot<T>(source);
}

ตัวอย่างการใช้งาน:

private static IDictionary<Guid, Subscriber> _subscribers;
private static EnumerableSnapshot<Subscriber> _subscribersSnapshot;

//...(in the constructor)
_subscribers = new Dictionary<Guid, Subscriber>();
_subscribersSnapshot = _subscribers.Values.ToEnumerableSnapshot();

// ...(elsewere)
foreach (var subscriber in _subscribersSnapshot)
{
    //...
}

น่าเสียดายที่ความคิดนี้ไม่สามารถใช้ได้ในปัจจุบันกับคลาสDictionaryใน. NET Core 3.0 เนื่องจากคลาสนี้ไม่ได้ทำการรวบรวมคอลเลกชันได้รับการแก้ไขยกเว้นเมื่อมีการระบุและวิธีการRemoveและClearถูกเรียกใช้ ตู้คอนเทนเนอร์อื่น ๆ ที่ฉันตรวจสอบนั้นทำงานอย่างสม่ำเสมอ ฉันจะตรวจสอบระบบชั้นเรียนเหล่านี้: List<T>, Collection<T>, ObservableCollection<T>, HashSet<T>, SortedSet<T>, และDictionary<T,V> SortedDictionary<T,V>เฉพาะสองวิธีดังกล่าวข้างต้นของDictionaryคลาสใน. NET Core จะไม่ทำให้การแจงนับ


อัปเดต:ฉันแก้ไขปัญหาข้างต้นโดยเปรียบเทียบความยาวของแคชกับคอลเลกชันดั้งเดิมด้วย การแก้ไขนี้อนุมานว่าพจนานุกรมจะถูกส่งผ่านโดยตรงเป็นอาร์กิวเมนต์ที่EnumerableSnapshotคอนสตรัค 's และลักษณะเฉพาะของตนจะไม่ถูกซ่อนไว้โดย (ตัวอย่าง) dictionary.Select(e => e).ΤοEnumerableSnapshot()ฉายที่ชอบ:


สำคัญ:คลาสด้านบนไม่ปลอดภัยสำหรับเธรด มันตั้งใจที่จะใช้จากรหัสทำงานเฉพาะในหัวข้อเดียว


0

คุณสามารถคัดลอกวัตถุพจนานุกรมสมาชิกไปยังวัตถุพจนานุกรมชนิดเดียวกันจากนั้นทำซ้ำวัตถุพจนานุกรมชั่วคราวโดยใช้ foreach loop


(โพสต์นี้ดูเหมือนจะไม่ได้ให้คำตอบที่มีคุณภาพสำหรับคำถามโปรดแก้ไขคำตอบของคุณและหรือเพียงแค่โพสต์เป็นความคิดเห็นสำหรับคำถาม)
sɐunıɔןɐqɐp

0

ดังนั้นวิธีที่แตกต่างในการแก้ปัญหานี้คือแทนที่จะลบองค์ประกอบสร้างพจนานุกรมใหม่และเพิ่มองค์ประกอบที่คุณไม่ต้องการลบจากนั้นแทนที่พจนานุกรมเดิมด้วยพจนานุกรมใหม่ ฉันไม่คิดว่านี่เป็นปัญหาประสิทธิภาพมากเกินไปเพราะจะไม่เพิ่มจำนวนครั้งที่คุณทำซ้ำโครงสร้าง


0

มีลิงก์เดียวที่ให้รายละเอียดดีมาก & ยังได้คำตอบ ลองถ้าคุณมีวิธีการแก้ปัญหาที่เหมาะสมกรุณาโพสต์ที่นี่เพื่อให้คนอื่นเข้าใจ วิธีการแก้ปัญหาที่กำหนดก็โอเคแล้วเช่นเดียวกับการโพสต์อื่น ๆ สามารถลองวิธีการแก้ปัญหาเหล่านี้

สำหรับคุณอ้างอิงลิงค์เดิม: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

เมื่อเราใช้คลาส. การทำให้เป็นอนุกรมสุทธิเพื่อทำให้เป็นอันดับวัตถุที่คำจำกัดความประกอบด้วยประเภท Enumerable เช่นการสะสมคุณจะได้รับ InvalidOperationException อย่างง่าย ๆ ว่า "การรวบรวมถูกแก้ไขการดำเนินการแจงนับอาจไม่ทำงาน" โดยที่การเข้ารหัสของคุณอยู่ภายใต้สถานการณ์หลายเธรด สาเหตุด้านล่างคือคลาสการทำให้เป็นอนุกรมจะวนซ้ำผ่านการรวบรวมผ่านตัวแจงนับเช่นปัญหาจะพยายามทำซ้ำผ่านการรวบรวมในขณะที่ปรับเปลี่ยน

วิธีแก้ปัญหาแรกเราสามารถใช้การล็อกเป็นโซลูชันการซิงโครไนซ์เพื่อให้แน่ใจว่าการดำเนินการกับวัตถุรายการสามารถเรียกใช้ได้จากเธรดครั้งเดียวเท่านั้น เห็นได้ชัดว่าคุณจะได้รับประสิทธิภาพการลงโทษว่าหากคุณต้องการทำให้เป็นชุดของวัตถุนั้นเป็นลำดับจากนั้นสำหรับแต่ละวัตถุล็อคจะถูกนำไปใช้

. Net 4.0 ซึ่งทำให้การจัดการกับสถานการณ์แบบมัลติเธรดมีประโยชน์ สำหรับปัญหาการรวบรวมอนุกรมฟิลด์นี้ฉันพบว่าเราสามารถรับประโยชน์จากคลาส ConcurrentQueue (Check MSDN) ซึ่งเป็นคอลเลกชันที่ปลอดภัยต่อเธรดและ FIFO และทำให้ไม่มีการล็อครหัส

การใช้คลาสนี้ในความเรียบง่ายสิ่งที่คุณต้องแก้ไขสำหรับรหัสของคุณกำลังเปลี่ยนประเภทคอลเลกชันด้วยใช้ Enqueue เพื่อเพิ่มองค์ประกอบไปยังส่วนท้ายของ ConcurrentQueue ลบรหัสล็อคเหล่านั้น หรือหากสถานการณ์ที่คุณกำลังดำเนินการอยู่นั้นจำเป็นต้องมีสิ่งที่รวบรวมเช่นรายการคุณจะต้องใช้รหัสเพิ่มเติมอีกสองสามรายการเพื่อปรับ ConcurrentQueue ในฟิลด์ของคุณ

BTW, ConcurrentQueue ไม่มีวิธีการที่ชัดเจนเนื่องจากอัลกอริทึมพื้นฐานที่ไม่อนุญาตให้มีการล้างคอลเลกชันแบบอะตอม ดังนั้นคุณต้องทำด้วยตัวเองวิธีที่เร็วที่สุดคือการสร้าง ConcurrentQueue ที่ว่างเปล่าใหม่เพื่อทดแทน


-1

โดยส่วนตัวฉันได้พบกับโค้ดที่ไม่คุ้นเคยเมื่อเร็ว ๆ นี้ซึ่งอยู่ใน dbcontext แบบเปิดพร้อมด้วย. Remove (รายการ) ของ Linq ฉันมีเวลาค้นหาข้อผิดพลาดในการดีบั๊กเนื่องจากมีการลบรายการในครั้งแรกซ้ำแล้วซ้ำอีกและถ้าฉันพยายามย้อนกลับและผ่านไปอีกครั้งคอลเล็กชันนั้นว่างเปล่าเนื่องจากฉันอยู่ในบริบทเดียวกัน!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.