private async Task CopyToAsyncCore(Stream destination, byte[] buffer, CancellationToken cancellationToken)
{
_state.PinReceiveBuffer(buffer);
CancellationTokenRegistration ctr = cancellationToken.Register(s => ((WinHttpResponseStream)s).CancelPendingResponseStreamReadOperation(), this);
_state.AsyncReadInProgress = true;
try
{
// Loop until there's no more data to be read
while (true)
{
// Query for data available
lock (_state.Lock)
{
if (!Interop.WinHttp.WinHttpQueryDataAvailable(_requestHandle, IntPtr.Zero))
{
throw new IOException(SR.net_http_io_read, WinHttpException.CreateExceptionUsingLastError());
}
}
int bytesAvailable = await _state.LifecycleAwaitable;
if (bytesAvailable == 0)
{
break;
}
Debug.Assert(bytesAvailable > 0);
// Read the available data
cancellationToken.ThrowIfCancellationRequested();
lock (_state.Lock)
{
if (!Interop.WinHttp.WinHttpReadData(_requestHandle, Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0), (uint)Math.Min(bytesAvailable, buffer.Length), IntPtr.Zero))
{
throw new IOException(SR.net_http_io_read, WinHttpException.CreateExceptionUsingLastError());
}
}
int bytesRead = await _state.LifecycleAwaitable;
if (bytesRead == 0)
{
break;
}
Debug.Assert(bytesRead > 0);
// Write that data out to the output stream
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
}
}
finally
{
_state.AsyncReadInProgress = false;
ctr.Dispose();
ArrayPool<byte>.Shared.Return(buffer);
}
// Leaving buffer pinned as it is in ReadAsync. It'll get unpinned when another read
// request is made with a different buffer or when the state is cleared.
}