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

WriteAsyncInternal() private method

private WriteAsyncInternal ( byte array, int offset, int numBytes, CancellationToken cancellationToken ) : Task
array byte
offset int
numBytes int
cancellationToken CancellationToken
return Task
        private Task WriteAsyncInternal(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.WriteAsync(array, offset, numBytes, cancellationToken);
            }

            if (!CanWrite) throw Error.GetWriteNotSupported();

            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
            Debug.Assert(!_isPipe || (_readPos == 0 && _readLength == 0), "Win32FileStream must not have buffered data here!  Pipes should be unidirectional.");

            bool writeDataStoredInBuffer = false;
            if (!_isPipe) // avoid async buffering with pipes, as doing so can lead to deadlocks (see comments in ReadInternalAsyncCore)
            {
                // Ensure the buffer is clear for writing
                if (_writePos == 0)
                {
                    if (_readPos < _readLength)
                    {
                        FlushReadBuffer();
                    }
                    _readPos = 0;
                    _readLength = 0;
                }

                // Determine how much space remains in the buffer
                int remainingBuffer = _bufferLength - _writePos;
                Debug.Assert(remainingBuffer >= 0);

                // Simple/common case:
                // - The write is smaller than our buffer, such that it's worth considering buffering it.
                // - There's no active flush operation, such that we don't have to worry about the existing buffer being in use.
                // - And the data we're trying to write fits in the buffer, meaning it wasn't already filled by previous writes.
                // In that case, just store it in the buffer.
                if (numBytes < _bufferLength && !HasActiveBufferOperation && numBytes <= remainingBuffer)
                {
                    Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, numBytes);
                    _writePos += numBytes;
                    writeDataStoredInBuffer = true;

                    // There is one special-but-common case, common because devs often use
                    // byte[] sizes that are powers of 2 and thus fit nicely into our buffer, which is
                    // also a power of 2. If after our write the buffer still has remaining space,
                    // then we're done and can return a completed task now.  But if we filled the buffer
                    // completely, we want to do the asynchronous flush/write as part of this operation 
                    // rather than waiting until the next write that fills the buffer.
                    if (numBytes != remainingBuffer)
                        return Task.CompletedTask;

                    Debug.Assert(_writePos == _bufferLength);
                }
            }

            // At this point, at least one of the following is true:
            // 1. There was an active flush operation (it could have completed by now, though).
            // 2. The data doesn't fit in the remaining buffer (or it's a pipe and we chose not to try).
            // 3. We wrote all of the data to the buffer, filling it.
            //
            // If there's an active operation, we can't touch the current buffer because it's in use.
            // That gives us a choice: we can either allocate a new buffer, or we can skip the buffer
            // entirely (even if the data would otherwise fit in it).  For now, for simplicity, we do
            // the latter; it could also have performance wins due to OS-level optimizations, and we could
            // potentially add support for PreAllocatedOverlapped due to having a single buffer. (We can
            // switch to allocating a new buffer, potentially experimenting with buffer pooling, should
            // performance data suggest it's appropriate.)
            //
            // If the data doesn't fit in the remaining buffer, it could be because it's so large
            // it's greater than the entire buffer size, in which case we'd always skip the buffer,
            // or it could be because there's more data than just the space remaining.  For the latter
            // case, we need to issue an asynchronous write to flush that data, which then turns this into
            // the first case above with an active operation.
            //
            // If we already stored the data, then we have nothing additional to write beyond what
            // we need to flush.
            //
            // In any of these cases, we have the same outcome:
            // - If there's data in the buffer, flush it by writing it out asynchronously.
            // - Then, if there's any data to be written, issue a write for it concurrently.
            // We return a Task that represents one or both.

            // Flush the buffer asynchronously if there's anything to flush
            Task flushTask = null;
            if (_writePos > 0)
            {
                flushTask = FlushWriteAsync(cancellationToken);

                // If we already copied all of the data into the buffer,
                // simply return the flush task here.  Same goes for if the task has 
                // already completed and was unsuccessful.
                if (writeDataStoredInBuffer ||
                    flushTask.IsFaulted ||
                    flushTask.IsCanceled)
                {
                    return flushTask;
                }
            }

            Debug.Assert(!writeDataStoredInBuffer);
            Debug.Assert(_writePos == 0);

            // Finally, issue the write asynchronously, and return a Task that logically
            // represents the write operation, including any flushing done.
            Task writeTask = WriteInternalCoreAsync(array, offset, numBytes, cancellationToken);
            return
                (flushTask == null || flushTask.Status == TaskStatus.RanToCompletion) ? writeTask :
                (writeTask.Status == TaskStatus.RanToCompletion) ? flushTask :
                Task.WhenAll(flushTask, writeTask);
        }