디버거를 연결할 때 발생하지 않기 때문에이 오류의 맨 아래에 도달 할 수 없습니다. 아래는 코드입니다.
이것은 Windows 서비스의 WCF 서버입니다. NotifySubscribers 메소드는 데이터 이벤트가있을 때마다 (임의의 간격으로, 하루에 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);
}
}
}
답변
일어날 수있는 일은 SignalData가 루프 중에 후드 아래에서 구독자 사전을 간접적으로 변경하여 해당 메시지로 이어지고 있다는 것입니다. 이를 변경하여이를 확인할 수 있습니다
foreach(Subscriber s in subscribers.Values)
에
foreach(Subscriber s in subscribers.Values.ToList())
내가 옳다면 문제는 사라질 것이다
호출 하면의 시작 부분에서 subscribers.Values.ToList()
값을 subscribers.Values
별도의 목록으로 복사 합니다 foreach
. 다른 사람은이 목록에 액세스 할 수 없으므로 (변수 이름조차 없습니다!), 루프 내에서 아무것도 수정할 수 없습니다.
답변
구독자가 구독을 취소하면 열거 중에 구독자 컬렉션의 내용이 변경됩니다.
이것을 수정하는 몇 가지 방법이 있습니다. 하나는 명시 적을 사용하도록 for 루프를 변경하는 것입니다 .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;
}
여기입니다 블로그 게시물 이 솔루션에 대한이.
그리고 StackOverflow에서 심층적 인 다이빙을하려면 : 왜이 에러가 발생합니까?
답변
실제로 문제는 목록에서 요소를 제거하고 아무 일도 없었던 것처럼 목록을 계속 읽을 것으로 기대하는 것 같습니다.
당신이 정말로해야 할 일은 끝에서 시작으로 시작하는 것입니다. 목록에서 요소를 제거하더라도 계속 읽을 수 있습니다.
답변
InvalidOperationException-
InvalidOperationException이 발생했습니다. foreach-loop에서 “컬렉션이 수정되었습니다”라고보고합니다
일단 객체가 제거되면 break 문을 사용하십시오.
전의:
ArrayList list = new ArrayList();
foreach (var item in list)
{
if(condition)
{
list.remove(item);
break;
}
}