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. }
public override Task <int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken token) { if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset)); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } if (count > buffer.Length - offset) { throw new ArgumentException(SR.net_http_buffer_insufficient_length, nameof(buffer)); } if (token.IsCancellationRequested) { return(Task.FromCanceled <int>(token)); } CheckDisposed(); if (_state.TcsReadFromResponseStream != null && !_state.TcsReadFromResponseStream.Task.IsCompleted) { throw new InvalidOperationException(SR.net_http_no_concurrent_io_allowed); } _state.PinReceiveBuffer(buffer); _state.TcsReadFromResponseStream = new TaskCompletionSource <int>(TaskCreationOptions.RunContinuationsAsynchronously); _state.TcsQueryDataAvailable = new TaskCompletionSource <int>(TaskCreationOptions.RunContinuationsAsynchronously); _state.TcsQueryDataAvailable.Task.ContinueWith((previousTask) => { if (previousTask.IsFaulted) { _state.TcsReadFromResponseStream.TrySetException(previousTask.Exception.InnerException); } else if (previousTask.IsCanceled || token.IsCancellationRequested) { _state.TcsReadFromResponseStream.TrySetCanceled(token); } else { int bytesToRead; int bytesAvailable = previousTask.Result; if (bytesAvailable > count) { bytesToRead = count; } else { bytesToRead = bytesAvailable; } lock (_state.Lock) { if (!Interop.WinHttp.WinHttpReadData( _requestHandle, Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset), (uint)bytesToRead, IntPtr.Zero)) { _state.TcsReadFromResponseStream.TrySetException( new IOException(SR.net_http_io_read, WinHttpException.CreateExceptionUsingLastError())); } } } }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); // TODO: Issue #2165. Register callback on cancellation token to cancel WinHTTP operation. lock (_state.Lock) { if (!Interop.WinHttp.WinHttpQueryDataAvailable(_requestHandle, IntPtr.Zero)) { _state.TcsReadFromResponseStream.TrySetException( new IOException(SR.net_http_io_read, WinHttpException.CreateExceptionUsingLastError())); } } return(_state.TcsReadFromResponseStream.Task); }