C# 收集被修改;枚举操作不能执行
我无法弄清这个错误的根源,因为当附加调试器时,它似乎不会发生 收集被修改;枚举操作不能执行 下面是代码 这是Windows服务中的WCF服务器。每当发生数据事件时,服务就会调用方法C# 收集被修改;枚举操作不能执行,c#,wcf,concurrency,dictionary,thread-safety,C#,Wcf,Concurrency,Dictionary,Thread Safety,我无法弄清这个错误的根源,因为当附加调试器时,它似乎不会发生 收集被修改;枚举操作不能执行 下面是代码 这是Windows服务中的WCF服务器。每当发生数据事件时,服务就会调用方法NotifySubscribers() 当Windows窗体客户端订阅时,订阅服务器ID将添加到订阅服务器字典中,当客户端取消订阅时,它将从字典中删除。该错误发生在客户端取消订阅时(或之后)。似乎下次调用NotifySubscribers()方法时,foreach()循环会失败,主题行出现错误。该方法将错误写入应用程序
NotifySubscribers()
当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);
}
}
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
公共类订阅服务器:ISubscriptionServer
{
专用静态IDictionary订户;
公共订阅服务器()
{
订阅者=新字典();
}
公共订阅服务器(DataRecord sr)
{
foreach(订阅服务器中的订阅服务器s.Values)
{
尝试
{
s、 信号数据(sr);
}
捕获(例外e)
{
DCS.WriteToApplicationLog(即消息、,
System.Diagnostics.EventLogEntryType.Error);
取消订阅事件(s.ClientId);
}
}
}
公共Guid SubscribeEvent(字符串clientDescription)
{
订户=新订户();
subscriber.Callback=OperationContext.Current。
GetCallbackChannel();
subscriber.Add(subscriber.ClientId,subscriber);
返回subscriber.ClientId;
}
公共无效取消订阅事件(Guid客户端ID)
{
尝试
{
订阅服务器。删除(clientId);
}
捕获(例外e)
{
System.Diagnostics.Debug.WriteLine(“取消订阅错误”+
e、 信息);
}
}
}
可能发生的情况是,SignalData
在循环过程中间接地改变了引擎盖下的订阅者词典,并导致了该消息。您可以通过更改
foreach(Subscriber s in subscribers.Values)
到
如果我是对的,问题就会消失
调用subscribers.Values.ToList()
将subscribers.Values
的值复制到foreach
开头的单独列表中。其他任何东西都无法访问此列表(它甚至没有变量名!),因此在循环中没有任何东西可以修改它。订阅者取消订阅时,您正在枚举期间更改订阅者集合的内容
有几种方法可以解决此问题,其中一种是将for循环更改为使用显式的.ToList()
:
在我看来,一个更有效的方法是另列一张清单,你声明你把任何“要删除”的东西都放进去。然后,在完成主循环(不带.ToList())之后,在“待删除”列表上执行另一个循环,在每次执行时删除每个条目。因此,在您的课堂上,您添加:
private List<Guid> toBeRemoved = new List<Guid>();
这不仅可以解决您的问题,还可以避免您必须从字典中不断创建列表,如果字典中有很多订户,那么创建列表的成本会很高。假设在任何给定的迭代中要删除的订阅者列表低于列表中的总数,这应该会更快。但当然,如果您的具体使用情况有任何疑问,请随时对其进行分析,以确保情况属实。实际上,在我看来,问题在于您正在从列表中删除元素,并希望继续阅读列表,就好像什么都没有发生一样
你真正需要做的是从头开始,从头开始。即使从列表中删除元素,您也可以继续阅读它。您还可以锁定订阅服务器词典,以防止在循环时对其进行修改:
lock (subscribers)
{
foreach (var subscriber in subscribers)
{
//do something
}
}
您可以将订阅服务器字典对象复制到同一类型的临时字典对象,然后使用foreach循环迭代临时字典对象 因此,解决此问题的另一种方法是创建一个新字典,只添加不想删除的元素,然后用新字典替换原始字典,而不是删除元素。我不认为这是一个太大的效率问题,因为它不会增加您在结构上的迭代次数。我也有同样的问题,当我使用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);
}
//foreach(itemsToBeLast中的var项)
for(int i=0;iitem.Detach);
if(匹配项!=null)
{
itemsToBeLast.移除(匹配项);
继续;
}
添加(itemsToBeLast[i]);/(附件);
}
为什么会出现此错误?
通常.Net集合不支持同时枚举和修改。如果在枚举期间尝试修改集合列表,则会引发异常。因此,这个错误背后的问题是,我们不能在循环相同的过程中修改列表/字典
解决方案之一
如果我们使用字典的键列表来迭代字典,我们可以并行地修改字典对象,就像我们在遍历键集合和
而不是字典(并迭代其键集合)
例子
//从di获取密钥集合
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
}
}
// 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);
}
//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;
}
ListItemCollection collection = new ListItemCollection();
foreach (ListItem item in ListBox1.Items)
{
if (item.Selected)
collection.Add(item);
}
ListItemCollection collection = new ListItemCollection();
foreach (ListItem item in ListBox1.Items)
{
if (item.Selected && !collection.Contains(item))
collection.Add(item);
}
ArrayList list = new ArrayList();
foreach (var item in list)
{
if(condition)
{
list.remove(item);
break;
}
}
for (int x = myList.Count - 1; x > -1; x--)
{
myList.RemoveAt(x);
}
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
}
}
//.........
}
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)
{
//...
}
while (list.Count > 0)
{
string Item = list[0];
list.RemoveAt(0);
// do here what you need to do with item
}
public IEnumerable<Data> GetInfo()
{
List<Data> info = null;
_cacheLock.EnterReadLock();
try
{
info = _cache.Values.SelectMany(ce => ce.Data); // Ad .Tolist() to avoid exc.
}
finally
{
_cacheLock.ExitReadLock();
}
return info;
}