Pergunta

Eu não posso chegar ao fundo deste erro, porque quando o depurador está ligado, ele não parece ocorrer. Abaixo está o código.

Este é um servidor WCF em um serviço do Windows. Os NotifySubscribers método é chamado pelo serviço sempre que houver um evento de dados (em intervalos aleatórios, mas não com muita frequência - cerca de 800 vezes por dia).

Quando um Windows Forms subscreve cliente, o ID do assinante é adicionado aos assinantes dicionário, e quando os unsubscribes cliente, ele será excluído do dicionário. O erro acontece quando (ou depois) um unsubscribes cliente. Parece que a próxima vez que o método NotifySubscribers () é chamado, o loop foreach () falha com o erro na linha de assunto. O método escreve o erro no registo de aplicação, como mostrado no código abaixo. Quando um depurador é anexado e um cliente unsubscribes, a executa código bem.

Você vê um problema com este código? Preciso fazer o dicionário 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);
        }
    }
}
Foi útil?

Solução

O que está acontecendo provável é que SignalData é indiretamente mudando os assinantes dicionário sob o capô durante o ciclo e levando a essa mensagem. Você pode verificar isso alterando

foreach(Subscriber s in subscribers.Values)

Para

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

Se eu estiver certo, o problema vai desaparecer

Chamando subscribers.Values.ToList () copia os valores de subscribers.Values ??para uma lista separada no início do foreach. Nada mais tem acesso a esta lista (que não tem sequer um nome de variável!), Então nada pode modificá-lo dentro do loop.

Outras dicas

Quando um assinante unsubscribes você está mudando conteúdo da coleção de Assinantes durante a enumeração.

Existem várias formas de corrigir isto, sendo um deles mudar o loop for para usar um .ToList() explícito:

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

A forma mais eficiente, na minha opinião, é ter uma outra lista que você declarar que você colocar qualquer coisa que está "a ser removido" em. Então, depois de terminar o seu loop principal (sem a .ToList ()), você faz outro loop sobre a lista "para ser removido", removendo cada entrada como isso acontece. Assim, em sua classe você adicionar:

private List<Guid> toBeRemoved = new List<Guid>();

Então você alterá-lo para:

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

Isto não só irá resolver o seu problema, ele irá impedi-lo de ter que manter a criação de uma lista de seu dicionário, que é caro, se há um grande número de assinantes lá. Assumindo que a lista de assinantes a serem removidos em qualquer dada iteração seja inferior ao número total na lista, este deve ser mais rápido. Mas é claro que se sente livre para o perfil para ter certeza de que o caso se houver qualquer dúvida em sua situação específica de utilização.

Você também pode bloquear seus assinantes dicionário para impedir que ele seja modificado sempre que o seu ser em loop:

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

Por que esse erro?

Em coleções .Net gerais não suportam ser enumeradas e modificado ao mesmo tempo. Se você tentar modificar a lista coleção durante a enumeração, levanta uma exceção. Assim, a questão por trás deste erro é que não pode modificar a lista / dicionário enquanto estamos loop através da mesma.

Uma das soluções

Se iterate um dicionário usando uma lista de suas chaves, em paralelo, podemos modificar o objeto de dicionário, como estamos a iteração através da chave de coleta e não o dicionário (e repetir seu entrega das chaves).

Exemplo

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

Aqui é um < em> blog sobre esta solução.

E para um mergulho profundo em StackOverflow: Por que ocorre este erro

Na verdade, o problema parece-me que você está removendo elementos da lista e com a expectativa de continuar a ler a lista como se nada tivesse acontecido.

O que você realmente precisa fazer é começar a partir do final e de volta para o início. Mesmo se você remover elementos da lista que você será capaz de continuar a lê-lo.

InvalidOperationException - Um InvalidOperationException ocorreu. Ele relata uma "coleção foi modificada" em um foreach loop

Use instrução break, Uma vez que o objeto é removido.

ex:

ArrayList list = new ArrayList(); 

foreach (var item in list)
{
    if(condition)
    {
        list.remove(item);
        break;
    }
}

Eu tive o mesmo problema, e foi resolvido quando eu usei um loop for vez de 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);
}

Eu vi muitas opções para isso, mas para mim este foi o melhor.

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

Em seguida, basta loop através da coleção.

Esteja ciente de que um ListItemCollection pode conter duplicatas. Por padrão não há nada duplicatas que impedem que está sendo adicionado à coleção. Para duplicatas evitá-lo pode fazer isso:

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

Ok então o que me ajudou foi a iteração para trás. Eu estava tentando remover uma entrada a partir de uma lista, mas iteração para cima e asneira do loop porque a entrada não existe mais:

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

                            myList.RemoveAt(x);

                        }

Você pode copiar assinantes objeto de dicionário para um mesmo tipo de objeto de dicionário temporário e, em seguida, iterate o objeto dicionário temporário usando loop foreach.

Assim, uma maneira diferente de resolver este problema seria em vez de remover os elementos criar um novo dicionário e só adicionar os elementos que você não queria remover, em seguida, substituir o dicionário original com o novo. Eu não acho que isso é demais de um problema de eficiência, porque não aumentar o número de vezes que você iterar sobre a estrutura.

Existe um link onde elaborou muito bem & solução também é dada. Experimente se você tem solução adequada, por favor postar aqui para que outros possam entender. solução dada é ok, em seguida, como o post para outro pode tentar estes solução.

para você referência link original: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

Quando usar classes .NET serialização para serializar um objeto em sua definição contém um tipo Enumerable, ou seja, coleção, você será facilmente obter InvalidOperationException dizendo "coleção foi modificada; operação de enumeração não pode executar", onde sua codificação está sob cenários multi-thread. A causa fundamental é que as classes de serialização vai iterar recolha através enumerador, como tal, problema vai para tentar para percorrer uma coleção enquanto modificando-o.

Primeiro solução, podemos simplesmente usar o bloqueio como uma solução de sincronização para garantir que a operação para o objeto de lista só pode ser executado a partir de um segmento de cada vez. Obviamente, você receberá penalidade de desempenho que se você quiser serializar uma coleção desse objeto, em seguida, para cada um deles, o bloqueio será aplicada.

Bem, .Net 4.0 que faz com que lidar com cenários de multi-threading úteis. para este serialização problema campo coleção, descobri que podemos apenas tirar partido ConcurrentQueue (Confira MSDN) classe, que é uma coleção thread-safe e FIFO e faz código livre-lock.

Usando essa classe, em sua simplicidade, as coisas que você precisa modificar o seu código está substituindo tipo de coleção com ele, usar Enqueue para adicionar um elemento ao final do ConcurrentQueue, remova os código de bloqueio. Ou, se o cenário que está a trabalhar exigem coisas coleção como List, você vai precisar de alguns código mais para se adaptar ConcurrentQueue em seus campos.

BTW, ConcurrentQueue doesnât tem um método claro devido ao algoritmo subjacente que permitem doesnât atomicamente clearing da coleção. então você tem que fazê-lo sozinho, a maneira mais rápida é a re-criar uma nova ConcurrentQueue vazio para uma substituição.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top