2009-12-07 3 views
2

J'ai une classe qui hérite de MemoryStream afin de fournir une mise en mémoire tampon. La classe fonctionne exactement comme prévu mais de temps en temps j'obtiens une exception InvalidOperationException pendant une lecture avec le message d'erreur étantLe verrouillage d'une liste peut-il échouer

La collection a été modifiée; l'opération d'énumération peut ne pas s'exécuter.

Mon code est ci-dessous et la seule ligne qui énumère une collection semblerait être:

m_buffer = m_buffer.Skip(count).ToList(); 

Cependant, je dois cela et toutes les autres opérations qui peuvent modifier l'objet m_buffer dans les serrures, donc je suis mystifié quant à la façon dont une opération d'écriture pourrait interférer avec une lecture pour provoquer cette exception?

public class MyMemoryStream : MemoryStream 
{ 
    private ManualResetEvent m_dataReady = new ManualResetEvent(false); 
    private List<byte> m_buffer = new List<byte>(); 

    public override void Write(byte[] buffer, int offset, int count) 
    { 
     lock (m_buffer) 
     { 
      m_buffer.AddRange(buffer.ToList().Skip(offset).Take(count)); 
     } 
     m_dataReady.Set(); 
    } 

    public override int Read(byte[] buffer, int offset, int count) 
    { 
     if (m_buffer.Count == 0) 
     { 
      // Block until the stream has some more data. 
      m_dataReady.Reset(); 
      m_dataReady.WaitOne(); 
     } 

     lock (m_buffer) 
     { 
      if (m_buffer.Count >= count) 
      { 
       // More bytes available than were requested. 
       Array.Copy(m_buffer.ToArray(), 0, buffer, offset, count); 
       m_buffer = m_buffer.Skip(count).ToList(); 
       return count; 
      } 
      else 
      { 
       int length = m_buffer.Count; 
       Array.Copy(m_buffer.ToArray(), 0, buffer, offset, length); 
       m_buffer.Clear(); 
       return length; 
      } 
     } 
    } 
} 

Répondre

5

Je ne peux pas dire exactement ce qui ne va pas du code affiché, mais un peu d'une bizarrerie est que vous verrouillez sur m_buffer, mais remplacer le tampon, de sorte que la collection verrouillée n'est pas toujours la collection qui est être lu et modifié.

Il est conseillé d'utiliser un objet readonly privé dédié pour le verrouillage:

private readonly object locker = new object(); 

    // ... 
    lock(locker) 
    { 
     // ... 
    } 
3

Vous avez au moins une course là-bas de données: la méthode Read, si vous êtes préempté après la if(m_buffer.Count == 0) bloc et avant le lock, Count peut être 0 à nouveau. Vous devriez vérifier le compte dans la lock, et utiliser Monitor.Wait, Monitor.Pulse et/ou Monitor.PulseAll pour la coordination, comme attente signal/ceci:

// On Write 
lock(m_buffer) 
{ 
    // ... 
    Monitor.PulseAll(); 
} 

// On Read 
lock(m_buffer) 
{ 
    while(m_buffer.Count == 0) 
     Monitor.Wait(m_buffer); 
// ... 

Vous devez protéger tous les accès à m_buffer, et appelant m_buffer.Count n'est pas spécial à cet égard.

+0

C'est en effet une erreur, mais cela ne devrait pas entraîner l'exception InvalidOperationException. – SoftMemes

+0

Oui, ce ne sera pas le cas. C'est pourquoi j'ai dit "au moins un". Ce que vous avez mentionné dans votre réponse est très probablement à l'origine de ce problème. –

+0

Si j'attends dans la méthode Read contenant le verrou, comment pourrais-je acquérir le verrou dans la méthode Write? – sipwiz

0

Modifiez-vous le contenu de buffer dans un autre thread quelque part, je soupçonne que ce soit l'énumération donnant l'erreur plutôt que m_buffer.

Questions connexes