문제

디버거가 부착되면 발생하지 않는 것 같습니다. 아래는 코드입니다.

이것은 Windows 서비스의 WCF 서버입니다. 데이터 이벤트가있을 때마다 서비스에 의해 NotifySubScribers 메소드를 호출합니다 (임의의 간격으로 하루에 약 800 번).

Windows가 클라이언트 가입을 형성하면 가입자 ID가 가입자 사전에 추가되며 클라이언트가 구독을 취소하면 사전에서 삭제됩니다. 클라이언트가 구독을 취소 할 때 (또는 후에) 오류가 발생합니다. 다음에 NotifySubScriber () 메소드가 호출되면 제목 줄의 오류가 발생하면 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())

내가 옳다면 문제가 해소됩니다

Calling Subscriber.values.tolist ()는 구독자의 값을 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;
}

여기에 있습니다 블로그 게시물 이 솔루션에 대해.

그리고 stackoverflow의 깊은 다이빙을 위해 : 이 오류가 발생하는 이유는 무엇입니까?

실제로 문제는 목록에서 요소를 제거하고 아무 일도 일어나지 않은 것처럼 목록을 계속 읽을 것으로 기대하는 것 같습니다.

당신이 정말로해야 할 일은 끝에서 시작으로 시작하는 것입니다. 목록에서 요소를 제거하더라도 계속 읽을 수 있습니다.

InvalidoPerationException- 유효하지 않은 결과가 발생했습니다. "수집 수정"이 foreach-loop에서 "수정되었습니다"라고보고합니다.

객체가 제거되면 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);
            }

Okay so what helped me was iterating backwards. I was trying to remove an entry from a list but iterating upwards and it screwed up the loop because the entry didn't exist anymore:

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

                            myList.RemoveAt(x);

                        }

You can copy subscribers dictionary object to a same type of temporary dictionary object and then iterate the temporary dictionary object using foreach loop.

So a different way to solve this problem would be instead of removing the elements create a new dictionary and only add the elements you didnt want to remove then replace the original dictionary with the new one. I don't think this is too much of an efficiency problem because it does not increase the number of times you iterate over the structure.

There is one link where it elaborated very well & solution is also given. Try it if you got proper solution please post here so other can understand. Given solution is ok then like the post so other can try these solution.

for you reference original link :- https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

When we use .Net Serialization classes to serialize an object where its definition contains an Enumerable type, i.e. collection, you will be easily getting InvalidOperationException saying "Collection was modified; enumeration operation may not execute" where your coding is under multi-thread scenarios. The bottom cause is that serialization classes will iterate through collection via enumerator, as such, problem goes to trying to iterate through a collection while modifying it.

First solution, we can simply use lock as a synchronization solution to ensure that the operation to the List object can only be executed from one thread at a time. Obviously, you will get performance penalty that if you want to serialize a collection of that object, then for each of them, the lock will be applied.

Well, .Net 4.0 which makes dealing with multi-threading scenarios handy. for this serializing Collection field problem, I found we can just take benefit from ConcurrentQueue(Check MSDN)class, which is a thread-safe and FIFO collection and makes code lock-free.

Using this class, in its simplicity, the stuff you need to modify for your code are replacing Collection type with it, use Enqueue to add an element to the end of ConcurrentQueue, remove those lock code. Or, if the scenario you are working on do require collection stuff like List, you will need a few more code to adapt ConcurrentQueue into your fields.

BTW, ConcurrentQueue doesnât have a Clear method due to underlying algorithm which doesnât permit atomically clearing of the collection. so you have to do it yourself, the fastest way is to re-create a new empty ConcurrentQueue for a replacement.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top