System.IO.FileStream.ReadAsyncInternal C# (CSharp) Method

ReadAsyncInternal() private method

private ReadAsyncInternal ( byte array, int offset, int numBytes, CancellationToken cancellationToken ) : Task
array byte
offset int
numBytes int
cancellationToken CancellationToken
return Task
        private Task<int> ReadAsyncInternal(byte[] array, int offset, int numBytes, CancellationToken cancellationToken)
        {
            // If async IO is not supported on this platform or 
            // if this Win32FileStream was not opened with FileOptions.Asynchronous.
            if (!_useAsyncIO)
            {
                return base.ReadAsync(array, offset, numBytes, cancellationToken);
            }

            if (!CanRead) throw Error.GetReadNotSupported();

            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");

            if (_isPipe)
            {
                // Pipes are tricky, at least when you have 2 different pipes
                // that you want to use simultaneously.  When redirecting stdout
                // & stderr with the Process class, it's easy to deadlock your
                // parent & child processes when doing writes 4K at a time.  The
                // OS appears to use a 4K buffer internally.  If you write to a
                // pipe that is full, you will block until someone read from 
                // that pipe.  If you try reading from an empty pipe and 
                // Win32FileStream's ReadAsync blocks waiting for data to fill it's 
                // internal buffer, you will be blocked.  In a case where a child
                // process writes to stdout & stderr while a parent process tries
                // reading from both, you can easily get into a deadlock here.
                // To avoid this deadlock, don't buffer when doing async IO on
                // pipes.  But don't completely ignore buffered data either.  
                if (_readPos < _readLength)
                {
                    int n = _readLength - _readPos;
                    if (n > numBytes) n = numBytes;
                    Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n);
                    _readPos += n;

                    // Return a completed task
                    return TaskFromResultOrCache(n);
                }
                else
                {
                    Debug.Assert(_writePos == 0, "Win32FileStream must not have buffered write data here!  Pipes should be unidirectional.");
                    return ReadNativeAsync(array, offset, numBytes, 0, cancellationToken);
                }
            }

            Debug.Assert(!_isPipe, "Should not be a pipe.");

            // Handle buffering.
            if (_writePos > 0) FlushWriteBuffer();
            if (_readPos == _readLength)
            {
                // I can't see how to handle buffering of async requests when 
                // filling the buffer asynchronously, without a lot of complexity.
                // The problems I see are issuing an async read, we do an async 
                // read to fill the buffer, then someone issues another read 
                // (either synchronously or asynchronously) before the first one 
                // returns.  This would involve some sort of complex buffer locking
                // that we probably don't want to get into, at least not in V1.
                // If we did a sync read to fill the buffer, we could avoid the
                // problem, and any async read less than 64K gets turned into a
                // synchronous read by NT anyways...       -- 

                if (numBytes < _bufferLength)
                {
                    Task<int> readTask = ReadNativeAsync(GetBuffer(), 0, _bufferLength, 0, cancellationToken);
                    _readLength = readTask.GetAwaiter().GetResult();
                    int n = _readLength;
                    if (n > numBytes) n = numBytes;
                    Buffer.BlockCopy(GetBuffer(), 0, array, offset, n);
                    _readPos = n;

                    // Return a completed task (recycling the one above if possible)
                    return (_readLength == n ? readTask : TaskFromResultOrCache(n));
                }
                else
                {
                    // Here we're making our position pointer inconsistent
                    // with our read buffer.  Throw away the read buffer's contents.
                    _readPos = 0;
                    _readLength = 0;
                    return ReadNativeAsync(array, offset, numBytes, 0, cancellationToken);
                }
            }
            else
            {
                int n = _readLength - _readPos;
                if (n > numBytes) n = numBytes;
                Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n);
                _readPos += n;

                if (n >= numBytes)
                {
                    // Return a completed task
                    return TaskFromResultOrCache(n);
                }
                else
                {
                    // For streams with no clear EOF like serial ports or pipes
                    // we cannot read more data without causing an app to block
                    // incorrectly.  Pipes don't go down this path 
                    // though.  This code needs to be fixed.
                    // Throw away read buffer.
                    _readPos = 0;
                    _readLength = 0;
                    return ReadNativeAsync(array, offset + n, numBytes - n, n, cancellationToken);
                }
            }
        }