public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
if (buffer.Length - offset < count)
throw new ArgumentException(SR.Argument_InvalidOffLen);
// Fast path check for cancellation already requested
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled<int>(cancellationToken);
EnsureNotClosed();
EnsureCanRead();
int bytesFromBuffer = 0;
// Try to satisfy the request from the buffer synchronously. But still need a sem-lock in case that another
// Async IO Task accesses the buffer concurrently. If we fail to acquire the lock without waiting, make this
// an Async operation.
SemaphoreSlim sem = LazyEnsureAsyncActiveSemaphoreInitialized();
Task semaphoreLockTask = sem.WaitAsync();
if (semaphoreLockTask.Status == TaskStatus.RanToCompletion)
{
bool completeSynchronously = true;
try
{
Exception error;
bytesFromBuffer = ReadFromBuffer(buffer, offset, count, out error);
// If we satisfied enough data from the buffer, we can complete synchronously.
// Reading again for more data may cause us to block if we're using a device with no clear end of file,
// such as a serial port or pipe. If we blocked here and this code was used with redirected pipes for a
// process's standard output, this can lead to deadlocks involving two processes.
// BUT - this is a breaking change.
// So: If we could not read all bytes the user asked for from the buffer, we will try once from the underlying
// stream thus ensuring the same blocking behaviour as if the underlying stream was not wrapped in this BufferedStream.
completeSynchronously = (bytesFromBuffer == count || error != null);
if (completeSynchronously)
{
return (error == null)
? LastSyncCompletedReadTask(bytesFromBuffer)
: Task.FromException<int>(error);
}
}
finally
{
if (completeSynchronously) // if this is FALSE, we will be entering ReadFromUnderlyingStreamAsync and releasing there.
sem.Release();
}
}
// Delegate to the async implementation.
return ReadFromUnderlyingStreamAsync(buffer, offset + bytesFromBuffer, count - bytesFromBuffer, cancellationToken,
bytesFromBuffer, semaphoreLockTask);
}