private async Task<int> ReadFromUnderlyingStreamAsync(byte[] array, int offset, int count,
CancellationToken cancellationToken,
int bytesAlreadySatisfied,
Task semaphoreLockTask)
{
// Same conditions validated with exceptions in ReadAsync:
// (These should be Debug.Requires(..) but that method had some issues in async methods; using Assert(..) for now.)
Debug.Assert(array != null);
Debug.Assert(offset >= 0);
Debug.Assert(count >= 0);
Debug.Assert(array.Length - offset >= count);
Debug.Assert(_stream != null);
Debug.Assert(_stream.CanRead);
Debug.Assert(_bufferSize > 0);
Debug.Assert(semaphoreLockTask != null);
// Employ async waiting based on the same synchronization used in BeginRead of the abstract Stream.
await semaphoreLockTask.ConfigureAwait(false);
try
{
// The buffer might have been changed by another async task while we were waiting on the semaphore.
// Check it now again.
int bytesFromBuffer = ReadFromBuffer(array, offset, count);
if (bytesFromBuffer == count)
return bytesAlreadySatisfied + bytesFromBuffer;
if (bytesFromBuffer > 0)
{
count -= bytesFromBuffer;
offset += bytesFromBuffer;
bytesAlreadySatisfied += bytesFromBuffer;
}
Debug.Assert(_readLen == _readPos);
_readPos = _readLen = 0;
// If there was anything in the write buffer, clear it.
if (_writePos > 0)
await FlushWriteAsync(cancellationToken).ConfigureAwait(false); // no Begin-End read version for Flush. Use Async.
// If the requested read is larger than buffer size, avoid the buffer and still use a single read:
if (count >= _bufferSize)
{
return bytesAlreadySatisfied + await _stream.ReadAsync(array, offset, count, cancellationToken).ConfigureAwait(false);
}
// Ok. We can fill the buffer:
EnsureBufferAllocated();
_readLen = await _stream.ReadAsync(_buffer, 0, _bufferSize, cancellationToken).ConfigureAwait(false);
bytesFromBuffer = ReadFromBuffer(array, offset, count);
return bytesAlreadySatisfied + bytesFromBuffer;
}
finally
{
SemaphoreSlim sem = LazyEnsureAsyncActiveSemaphoreInitialized();
sem.Release();
}
}