我不能让这错误的,因为当调试器连接,它似乎不会发生。下面是代码。

这是一个WCF服务器在Windows服务。该方法NotifySubscribers是所谓的服务,只要存在数据的事件(随机间隔时间,但不是非常经常约为800次,每天)。

当Windows形式客户订阅、订阅者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);
        }
    }
}
有帮助吗?

解决方案

可能发生的事情是SignalData在循环期间间接改变了引擎盖下的订阅者字典并导致该消息。您可以通过更改

来验证这一点
foreach(Subscriber s in subscribers.Values)

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

如果我是对的,问题就会消失

调用subscriber.Values.ToList()将subscriber.Values的值复制到foreach开头的单独列表中。没有其他东西可以访问这个列表(它甚至没有变量名!),所以没有什么可以在循环内修改它。

其他提示

当用户取消订阅你们改变内容,收集用户在枚举。

有几种方法来解决这个问题,一个是改变对环使用明确 .ToList():

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

在我看来,更有效的方法是使用另一个列表,声明您放置了“要删除”的内容。成。然后在完成主循环(没有.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 );
}

这不仅可以解决您的问题,还可以防止您不得不继续从字典中创建列表,如果有很多订阅者,这会很昂贵。假设在任何给定迭代中要删除的订户列表低于列表中的总数,这应该更快。但是,如果对您的具体使用情况有任何疑问,当然可以随意对其进行分析,以确定情况。

您还可以锁定订阅者字典,以防止它在循环时被修改:

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

为什么会出现此错误?

通常,.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;
}

以下是 <关于此解决方案的em>博客文章

深入了解StackOverflow:为何会出现此错误?

实际上,在我看来,问题是您要从列表中删除元素并期望继续读取列表,就像没有发生任何事情一样。

你真正需要做的是从结束开始,然后回到开始。即使您从列表中删除元素,您也可以继续阅读它。

<强>出现InvalidOperationException - 发生InvalidOperationException。它报告“收集被修改”。在foreach循环中

使用break语句,删除对象后。

前:

ArrayList list = new ArrayList(); 

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

我遇到了同样的问题,当我使用 for 循环代替 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);
            }

好吧所以帮助我的是向后迭代。我试图从列表中删除一个条目,但向上迭代,它搞砸了循环因为条目不再存在:

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

                            myList.RemoveAt(x);

                        }

您可以将订阅者字典对象复制到相同类型的临时字典对象,然后使用foreach循环迭代临时字典对象。

因此,解决此问题的另一种方法是,不是删除元素,而是创建新的字典,只添加您不想删除的元素,然后用新的字典替换原始字典。我认为这不是一个效率问题,因为它不会增加迭代结构的次数。

有一个链接,它很好地阐述了&amp;解决方案也给出了。 如果您有适当的解决方案,请尝试在此发布,以便其他人可以理解。 鉴于解决方案是好的,那么其他人可以尝试这些解决方案。

为您参考原始链接: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

当我们使用.Net Serialization类来序列化其定义包含Enumerable类型的对象时,即 收集,你将很容易得到InvalidOperationException说“收集被修改; 枚举操作可能不会执行“你的编码在多线程场景下。 最根本的原因是序列化类将通过枚举器迭代收集,因此, 问题是尝试在修改它时迭代一个集合。

首先,我们可以简单地使用lock作为同步解决方案来确保 List对象的操作一次只能从一个线程执行。  显然,你将获得性能损失 如果要序列化该对象的集合,那么对于每个对象,将应用锁定。

那么,.Net 4.0可以轻松处理多线程场景。 对于这个序列化Collection字段问题,我发现我们可以从ConcurrentQueue(Check MSDN)类中受益, 这是一个线程安全的FIFO集合,可以使代码无锁。

使用这个类,简单来说,你需要修改代码的东西正在用它替换Collection类型, 使用Enqueue将一个元素添加到ConcurrentQueue的末尾,删除这些锁定代码。 或者,如果您正在处理的场景确实需要像List这样的集合,那么您将需要更多代码来使ConcurrentQueue适应您的字段。

BTW,ConcurrentQueue没有Clear方法,因为基础算法不允许以原子方式清除集合。 所以你必须自己动手,最快的方法是重新创建一个新的空ConcurrentQueue进行替换。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top