none
Stream.BeginWrite blocks RRS feed

  • Question

  • Hello,

    When using .NET 4.0 or 4.5 on Win 8, the default implementation of System.IO.Stream.BeginWrite, it will sometimes block forever. I can confirm in the debugger that BeginWrite doesn't return and my own stream Write(byte[], int, int) method doesn't get called.

    Can someone identify what I'm doing wrong? It looks like the .NET internal implementation is waiting on a semaphore that doesn't fire.

    This is the callstack:

      mscorlib.dll!System.Threading.Monitor.Wait(obj, millisecondsTimeout, exitContext) + 0x16 bytes 
      mscorlib.dll!System.Threading.SemaphoreSlim.WaitUntilCountOrTimeout(millisecondsTimeout = -1, startTime = 0, cancellationToken) + 0x60 bytes 
      mscorlib.dll!System.Threading.SemaphoreSlim.Wait(millisecondsTimeout, cancellationToken) + 0x178 bytes 
      mscorlib.dll!System.IO.Stream.BeginWriteInternal(buffer = {byte[26]}, offset = 0, count = 26, callback = {Method = ??}, state = {RedJam.InetServices.StreamWriteString}, serializeAsynchronously) + 0x99 bytes 
      mscorlib.dll!System.IO.Stream.BeginWrite(buffer, offset, count, callback, state) + 0x16 bytes 
    > InetService.dll!RedJam.InetServices.StreamWriteString.SendNextLine(sw = {RedJam.InetServices.StreamWriteString}) Line 144 + 0x59 bytes C#
      InetService.dll!RedJam.InetServices.StreamWriteString.WriteCallback(ar = Id = ??, Status = ??, Method = ??, Result = ??) Line 156 + 0x8 bytes C#
      mscorlib.dll!System.IO.Stream.ReadWriteTask.InvokeAsyncCallback(completedTask) + 0x2c bytes 
      mscorlib.dll!System.Threading.ExecutionContext.RunInternal(executionContext, callback, state, preserveSyncCtx) + 0xa7 bytes 
      mscorlib.dll!System.Threading.ExecutionContext.Run(executionContext, callback, state, preserveSyncCtx) + 0x16 bytes 
      mscorlib.dll!System.IO.Stream.ReadWriteTask.System.Threading.Tasks.ITaskCompletionAction.Invoke(completingTask) + 0xab bytes 
      mscorlib.dll!System.Threading.Tasks.Task.FinishContinuations() + 0x221 bytes 
      mscorlib.dll!System.Threading.Tasks.Task.FinishStageThree() + 0x1c bytes 
      mscorlib.dll!System.Threading.Tasks.Task.FinishStageTwo() + 0x84 bytes 
      mscorlib.dll!System.Threading.Tasks.Task.Finish(bUserDelegateExecuted) + 0x30 bytes 
      mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(currentTaskSlot = Id = ??, Status = ??, Method =??, Result = ??) + 0xd6 bytes 
      mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bPreventDoubleExecution) + 0xb3 bytes 
      mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() + 0x7 bytes 
      mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() + 0x149 bytes 
      mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() + 0x5 bytes 
      [Native to Managed Transition] 
      [Appdomain Transition] 
      [Native to Managed Transition] 

    The code is very simple, and I can see that I'm always calling a EndWrite for every BeginWrite and everything is nicely serialized.

        internal sealed class StreamWriteString
        {
            private Stream m_Stream;
            private Encoding m_Encoding;
            private object m_WriteLock = new object();
            private LinkedList<string> m_Lines = new LinkedList<string>();
            private byte[] m_SendBuffer;
    
            public StreamWriteString(Stream stream, Encoding encoding)
            {
                m_Stream = stream;
                m_Encoding = encoding;
            }
    
            public void WriteLineAsync(string line)
            {
                lock (m_WriteLock) {
                    m_Lines.AddLast(line);
                    if (m_SendBuffer == null) SendNextLine(this);
                }
            }
    
            private static void SendNextLine(StreamWriteString sw)
            {
                string line = sw.m_Lines.First.Value;
                sw.m_Lines.RemoveFirst();
                sw.m_SendBuffer = sw.m_Encoding.GetBytes(line + "\r\n");
                Console.WriteLine("BeginWrite");
                IAsyncResult ar = sw.m_Stream.BeginWrite(sw.m_SendBuffer, 0, sw.m_SendBuffer.Length, WriteCallback, sw);
                Console.WriteLine("AR: Complete={0}, Synch={1}", ar.IsCompleted, ar.CompletedSynchronously);
            }
    
            private static void WriteCallback(IAsyncResult ar)
            {
                Console.WriteLine("WriteCallback");
                StreamWriteString sw = (StreamWriteString)ar.AsyncState;
                lock (sw.m_WriteLock) {
                    Console.WriteLine("EndWrite");
                    sw.m_Stream.EndWrite(ar);
                    if (sw.m_Lines.Count > 0) {
                        SendNextLine(sw);
                    } else {
                        sw.m_SendBuffer = null;
                    }
                }
            }
        }

    My logs show the following which is consistent:

    BeginWrite
    AR: Complete=False, Synch=False
    Server: Write(byte[], offset=0, count=26)
    Server: Write(byte[], offset=0, count=26) finished
    WriteCallback
    EndWrite
    BeginWrite
    AR: Complete=False, Synch=False
    Server: Write(byte[], offset=0, count=26)
    Server: Write(byte[], offset=0, count=26) finished
    WriteCallback
    EndWrite
    BeginWrite

    < blocks here >

    Sunday, April 7, 2013 6:59 PM

Answers

  • Hi,

    The blocking behavior occurs in the .NET framework. There are two solutions to the problem: the previous one to use Write() and Read() on their own independent threads, which is a bad idea if the stream happens to be opened asynchronously (e.g. NetworkStream, PipeStream or FileStream).

    The better idea is to use Asynchronous and have the stream itself support native BeginRead()/EndRead() and BeginWrite()/EndWrite(). So long as reads are independent of writes (which is not the case for streams that support seeking).

    See my analysis and solution on CodeProject: http://www.codeproject.com/Tips/575618/Avoiding-Deadlocks-with-System-IO-Stream-BeginRead

    The problem is with behavior that's documented only in the .NET framework code (it isn't on MSDN).

    // To avoid a race with a stream's position pointer & generating ----
      // conditions with internal buffer indexes in our own streams that 
      // don't natively support async IO operations when there are multiple
      // async requests outstanding, we will block the application's main 
      // thread if it does a second IO request until the first one completes. 
      var semaphore = EnsureAsyncActiveSemaphoreInitialized();
      Task semaphoreTask = null; 
      if (serializeAsynchronously) {
        ...
      } else { 
        semaphore.Wait(); // synchronously wait here 
      }
    

    • Marked as answer by Jason Curl Wednesday, April 10, 2013 4:32 PM
    Wednesday, April 10, 2013 4:31 PM

All replies

  • I've just downloaded the source for .NET 4.5, it appears the same semaphore is used for BeginRead and BeginWrite with System.IO.Stream. I'm assuming for now that this could be the cause of the problem (I still have to verify), but as I treat reading/writing as two independent operations and a BeginRead may not return for a very long time (it's intended to read user input), I'm not sure how I should design my program to allow the two to be independent of each other. Modifying the underlying stream is not an option, only the classes that I have above.
    Sunday, April 7, 2013 8:56 PM
  • Hi Jason,

    Based on my know, the Beginwrite and endWrite is a pair of an asynchronous operations, you don't  need the lock statement to synchronous. If you want to synchronous, please just use write: http://msdn.microsoft.com/en-us/library/system.io.stream.write.aspx rather than BeginWrite/EndWrite.

    Thanks.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, April 8, 2013 1:11 AM
    Moderator
  • Hi,

    The blocking behavior occurs in the .NET framework. There are two solutions to the problem: the previous one to use Write() and Read() on their own independent threads, which is a bad idea if the stream happens to be opened asynchronously (e.g. NetworkStream, PipeStream or FileStream).

    The better idea is to use Asynchronous and have the stream itself support native BeginRead()/EndRead() and BeginWrite()/EndWrite(). So long as reads are independent of writes (which is not the case for streams that support seeking).

    See my analysis and solution on CodeProject: http://www.codeproject.com/Tips/575618/Avoiding-Deadlocks-with-System-IO-Stream-BeginRead

    The problem is with behavior that's documented only in the .NET framework code (it isn't on MSDN).

    // To avoid a race with a stream's position pointer & generating ----
      // conditions with internal buffer indexes in our own streams that 
      // don't natively support async IO operations when there are multiple
      // async requests outstanding, we will block the application's main 
      // thread if it does a second IO request until the first one completes. 
      var semaphore = EnsureAsyncActiveSemaphoreInitialized();
      Task semaphoreTask = null; 
      if (serializeAsynchronously) {
        ...
      } else { 
        semaphore.Wait(); // synchronously wait here 
      }
    

    • Marked as answer by Jason Curl Wednesday, April 10, 2013 4:32 PM
    Wednesday, April 10, 2013 4:31 PM