수집이 수정되었습니다. 열거 작업이 실행되지 않을 수 있습니다 catch (Exception

디버거를 연결할 때 발생하지 않기 때문에이 오류의 맨 아래에 도달 할 수 없습니다. 아래는 코드입니다.

이것은 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;
    }
}