tags:

views:

49

answers:

3

I have a class that inherits from MemoryStream in order to provide some buffering. The class works exactly as expected but every now and then I get an InvalidOperationException during a Read with the error message being

Collection was modified; enumeration operation may not execute.

My code is below and the only line that enumerates a collection would seem to be:

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

However I have that and all other operations that can modify the m_buffer object within locks so I'm mystified as to how a Write operation could interfere with a Read to cause that 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;
            }
        }
    }
}
+5  A: 

I cannot say exactly what's going wrong from the code you posted, but a bit of an oddity is that you lock on m_buffer, but replace the buffer, so that the collection locked is not always the collection that is being read and modified.

It is good practice to use a dedicated private readonly object for the locking:

private readonly object locker = new object();

    // ...
    lock(locker)
    {
         // ...
    }
Freed
Good edit, thanks.
Freed
+3  A: 

You have at least one data race there: on the Read method , if you're pre-empted after the if(m_buffer.Count == 0) block and before the lock, Count can be 0 again. You should check the count inside the lock, and use Monitor.Wait, Monitor.Pulse and/or Monitor.PulseAll for the wait/signal coordination, like this:

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

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

You have to protect all accesses to m_buffer, and calling m_buffer.Count is not special in that regard.

Martinho Fernandes
That is indeed an error, but it shouldn't result in the InvalidOperationException.
Freed
Yeah, it won't. That was why I said "at least one". What you mentioned in your answer is most likely causing that problem.
Martinho Fernandes
If I'm waiting in the Read method holding the lock how would I be able to acquire the lock in the Write method?
sipwiz
Monitor.Wait releases the lock before blocking and re-acquires it before returning. Read the MSDN page.
Martinho Fernandes
Monitor.Wait releases the lock while waiting for a pulse. The reason you have to check the count again is that you cannot be sure that another thread did not run before the lock could be re-acquired when exiting the Wait call.
Freed
A: 

Do you modify the content of buffer in another thread somewhere, I suspect that may be the enumeration giving error rather than m_buffer.

Courtney de Lautour