CSharp - 集合已修改; 枚举操作可能无法执行

  显示原文与译文双语对照的内容

我无法访问这里错误的底部,因为当调试器被附加时,它似乎没有出现。 下面是代码。

这是 Windows 服务中的一个WCF服务器。 NotifySubscribers方法由服务调用事件( 随机间隔,但不经常- 大约每天 800次) 每当有数据。

当 Windows 窗体客户端订阅时,订阅者标识将被添加到订阅服务器词典中,当客户端退订时,它将从字典中删除。 当( 或者之后) 客户端退订时发生错误。 似乎下次调用 NotifySubscribers() 方法时,foreach() 循环会失败,在主题行中出现错误。 方法将错误写入应用程序日志中,如下面的代码所示。 当调试器被附加并且客户端退订时,代码执行得很好。

你看到这里代码有问题? 是否需要将字典设为 thread-safe?


[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在循环中间接更改了hood的订阅者词典,并导致该消息。 你可以通过更改


foreach(Subscriber s in subscribers.Values)


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

如果我是对的,问题就会发生

订阅服务器退订时,在枚举期间更改订阅服务器集合的内容。

有几种解决这个问题的方法,一个是将循环更改为:


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

在我看来,一个更有效的方法是,让另一个 List 声明,你把任何是"要删除"的东西放入。 然后,完成主循环( 没有。tolist ( ) 之后,在"要删除"List 上执行另一个循环,删除每个条目。 所以在你的类中添加:


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

这不仅可以解决你的问题,它将阻止你从字典中继续创建 List,如果有大量的订阅服务器,这将非常昂贵。 假定在任何给定迭代中删除的订阅服务器的List 低于 List 中的总数,这应该是更快的。 当然,如果你在特定的使用情况下有任何疑问,你可以自由地对它进行配置。

你还可以锁定订阅服务器字典,防止它在循环时被修改:


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

我有同样的问题,当我使用"用于"循环而不是foreach循环时它就被解决了


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

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

我认为这个错误背后的问题是,我们不能在我们正在遍历的同一个字典对象中进行更改。 但是,如果我们可以使用它的键的临时 List 来迭代字典,我们可以同时修改它。

示例:


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

关于它的博客帖子: 已经解决:集合被修改;枚举操作可能无法执行。

发生这里错误的原因: 应答

我已经看到了很多选项,但对于我来说这个是最好的。


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

然后简单地遍历集合。

...