internal static IAsyncOperationWithProgress<IBuffer, UInt32> ReadAsync_AbstractStream(Stream stream, IBuffer buffer, UInt32 count,
InputStreamOptions options)
{
Debug.Assert(stream != null);
Debug.Assert(stream.CanRead);
Debug.Assert(buffer != null);
Debug.Assert(buffer is IBufferByteAccess);
Debug.Assert(0 <= count);
Debug.Assert(count <= Int32.MaxValue);
Debug.Assert(count <= buffer.Capacity);
Debug.Assert(options == InputStreamOptions.None || options == InputStreamOptions.Partial || options == InputStreamOptions.ReadAhead);
Contract.EndContractBlock();
Int32 bytesRequested = (Int32)count;
// Check if the buffer is our implementation.
// IF YES: In that case, we can read directly into its data array.
// IF NO: The buffer is of unknown implementation. It's not backed by a managed array, but the wrapped stream can only
// read into a managed array. If we used the user-supplied buffer we would need to copy data into it after every read.
// The spec allows to return a buffer instance that is not the same as passed by the user. So, we will create an own
// buffer instance, read data *directly* into the array backing it and then return it to the user.
// Note: the allocation costs we are paying for the new buffer are unavoidable anyway, as we we would need to create
// an array to read into either way.
IBuffer dataBuffer = buffer as WindowsRuntimeBuffer;
if (dataBuffer == null)
dataBuffer = WindowsRuntimeBuffer.Create((Int32)Math.Min((UInt32)Int32.MaxValue, buffer.Capacity));
// This operation delegate will we run inside of the returned IAsyncOperationWithProgress:
Func<CancellationToken, IProgress<UInt32>, Task<IBuffer>> readOperation = async (cancelToken, progressListener) =>
{
// No bytes read yet:
dataBuffer.Length = 0;
// Get the buffer backing array:
Byte[] data;
Int32 offset;
bool managedBufferAssert = dataBuffer.TryGetUnderlyingData(out data, out offset);
Debug.Assert(managedBufferAssert);
// Init tracking values:
bool done = cancelToken.IsCancellationRequested;
Int32 bytesCompleted = 0;
// Loop until EOS, cancelled or read enough data according to options:
while (!done)
{
Int32 bytesRead = 0;
try
{
// Read asynchronously:
bytesRead = await stream.ReadAsync(data, offset + bytesCompleted, bytesRequested - bytesCompleted, cancelToken)
.ConfigureAwait(continueOnCapturedContext: false);
// We will continue here on a different thread when read async completed:
bytesCompleted += bytesRead;
// We will handle a cancelation exception and re-throw all others:
}
catch (OperationCanceledException)
{
// We assume that cancelToken.IsCancellationRequested is has been set and simply proceed.
// (we check cancelToken.IsCancellationRequested later)
Debug.Assert(cancelToken.IsCancellationRequested);
// This is because if the cancellation came after we read some bytes we want to return the results we got instead
// of an empty cancelled task, so if we have not yet read anything at all, then we can throw cancellation:
if (bytesCompleted == 0 && bytesRead == 0)
throw;
}
// Update target buffer:
dataBuffer.Length = (UInt32)bytesCompleted;
Debug.Assert(bytesCompleted <= bytesRequested);
// Check if we are done:
done = options == InputStreamOptions.Partial // If no complete read was requested, any amount of data is OK
|| bytesRead == 0 // this implies EndOfStream
|| bytesCompleted == bytesRequested // read all requested bytes
|| cancelToken.IsCancellationRequested; // operation was cancelled
// Call user Progress handler:
if (progressListener != null)
progressListener.Report(dataBuffer.Length);
} // while (!done)
// If we got here, then no error was detected. Return the results buffer:
return dataBuffer;
}; // readOperation
return AsyncInfo.Run<IBuffer, UInt32>(readOperation);
} // ReadAsync_AbstractStream