/// <inheritdoc /> public override async Task <int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { using (StreamOperationToken op = StartNewOperation(StreamOperation.Read)) { BackgroundOperationSlot backgroundOperationSlot = await op.WaitForBackgroundOperationSlotAsync(); Contract.Assume( internalBuffer.State != FileBuffer.BufferState.Locked, "Buffer should not be locked, since no background operation is running."); int bytesReadFromBuffer; bool fillStarted = ReadBufferAndStartFillIfEmptiedButUsable(backgroundOperationSlot, buffer, offset, count, out bytesReadFromBuffer); if (bytesReadFromBuffer == 0) { if (fillStarted) { backgroundOperationSlot = await op.WaitForBackgroundOperationSlotAsync(); } // We just ran a fill and waited for it (usability may be updated on its completion) or we were unable to start a fill. // In either case, we should now respond to usability. We don't have any bytes read, and so we are now in some sense at the position // where the unusability occurs, rather than behind it (e.g. we should quietly exhaust the buffer before complaining about EOF). switch (backgroundOperationSlot.Usability) { case StreamUsability.Usable: Contract.Assume( fillStarted, "ReadBufferAndStartFillIfEmptiedButUsable should have started a fill, since the stream is usable"); Analysis.IgnoreResult( ReadBufferAndStartFillIfEmptiedButUsable(backgroundOperationSlot, buffer, offset, count, out bytesReadFromBuffer)); Contract.Assume( bytesReadFromBuffer > 0, "Usable stream implies that the completed fill obtained bytes. Zero bytes returned from ReadFile implies failure."); break; case StreamUsability.EndOfFileReached: Contract.Assume( internalBuffer.State == FileBuffer.BufferState.Empty, "EndOfFileReached usability coincides with a totally-failed fill (nothing read)"); break; case StreamUsability.Broken: throw backgroundOperationSlot.ThrowExceptionForBrokenStream(); default: throw Contract.AssertFailure("Unhandled StreamUsability"); } } // We've satisfied a read request for 'bytesReadFromBuffer' bytes, which advances our virtual file position. op.AdvancePosition(bytesReadFromBuffer); return(bytesReadFromBuffer); } }