/// <summary> /// Enables efficiently reading a stream using <see cref="PipeReader"/>. /// </summary> /// <param name="stream">The stream to read from using a pipe.</param> /// <param name="sizeHint">A hint at the size of messages that are commonly transferred. Use 0 for a commonly reasonable default.</param> /// <param name="pipeOptions">Optional pipe options to use.</param> /// <param name="disposeWhenReaderCompleted">A task which, when complete, signals that this method should dispose of the <paramref name="stream"/>.</param> /// <param name="cancellationToken">A cancellation token that aborts reading from the <paramref name="stream"/>.</param> /// <returns>A <see cref="PipeReader"/>.</returns> /// <remarks> /// When the caller invokes <see cref="PipeReader.Complete(Exception)"/> on the result value, /// this leads to the associated <see cref="PipeWriter.Complete(Exception)"/> to be automatically called as well. /// </remarks> private static PipeReader UsePipeReader(this Stream stream, int sizeHint = 0, PipeOptions?pipeOptions = null, Task?disposeWhenReaderCompleted = null, CancellationToken cancellationToken = default) { Requires.NotNull(stream, nameof(stream)); Requires.Argument(stream.CanRead, nameof(stream), "Stream must be readable."); var pipe = new Pipe(pipeOptions ?? PipeOptions.Default); // Notice when the pipe reader isn't listening any more, and terminate our loop that reads from the stream. // OBSOLETE API USAGE NOTICE: If at some point we need to stop relying on PipeWriter.OnReaderCompleted (since it is deprecated and may be removed later), // we can return a decorated PipeReader that calls us from its Complete method directly. var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); #pragma warning disable CS0618 // Type or member is obsolete pipe.Writer.OnReaderCompleted( (ex, state) => { try { ((CancellationTokenSource)state !).Cancel(); } catch (AggregateException cancelException) { // .NET Core may throw this when canceling async I/O (https://github.com/dotnet/runtime/issues/39902). // Just swallow it. We've canceled what we intended to. cancelException.Handle(x => x is ObjectDisposedException); } }, combinedTokenSource); // When this argument is provided, it provides a means to ensure we don't hang while reading from an I/O pipe // that doesn't respect the CancellationToken. Disposing a Stream while reading is a means to terminate the ReadAsync operation. if (disposeWhenReaderCompleted is object) { disposeWhenReaderCompleted.ContinueWith( (_, s1) => { var tuple = (Tuple <Pipe, Stream>)s1 !; tuple.Item1.Writer.OnReaderCompleted((ex, s2) => ((Stream)s2 !).Dispose(), tuple.Item2); }, Tuple.Create(pipe, stream), cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Forget(); } #pragma warning restore CS0618 // Type or member is obsolete Task.Run(async delegate { while (!combinedTokenSource.Token.IsCancellationRequested) { Memory <byte> memory = pipe.Writer.GetMemory(sizeHint); try { int bytesRead = await stream.ReadAsync(memory, combinedTokenSource.Token).ConfigureAwait(false); if (bytesRead == 0) { break; } pipe.Writer.Advance(bytesRead); } catch (OperationCanceledException) { break; } catch (ObjectDisposedException) { break; } catch (Exception ex) { // Propagate the exception to the reader. await pipe.Writer.CompleteAsync(ex).ConfigureAwait(false); return; } FlushResult result = await pipe.Writer.FlushAsync().ConfigureAwait(false); if (result.IsCompleted) { break; } } // Tell the PipeReader that there's no more data coming await pipe.Writer.CompleteAsync().ConfigureAwait(false); }).Forget(); return(pipe.Reader); }
/// <summary> /// Initializes a new instance of the <see cref="MultiplexingStream"/> class. /// </summary> /// <param name="stream">The stream to multiplex multiple channels over. Use <see cref="FullDuplexStream.Splice(Stream, Stream)"/> if you have distinct input/output streams.</param> /// <param name="options">Options to define behavior for the multiplexing stream.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>The multiplexing stream, once the handshake is complete.</returns> /// <exception cref="EndOfStreamException">Thrown if the remote end disconnects before the handshake is complete.</exception> public static async Task <MultiplexingStream> CreateAsync(Stream stream, Options options, CancellationToken cancellationToken = default) { Requires.NotNull(stream, nameof(stream)); Requires.Argument(stream.CanRead, nameof(stream), "Stream must be readable."); Requires.Argument(stream.CanWrite, nameof(stream), "Stream must be writable."); options = options ?? new Options(); // Send the protocol magic number, and a random GUID to establish even/odd assignments. var randomSendBuffer = Guid.NewGuid().ToByteArray(); var sendBuffer = new byte[ProtocolMagicNumber.Length + randomSendBuffer.Length]; Array.Copy(ProtocolMagicNumber, sendBuffer, ProtocolMagicNumber.Length); Array.Copy(randomSendBuffer, 0, sendBuffer, ProtocolMagicNumber.Length, randomSendBuffer.Length); Task writeTask = stream.WriteAsync(sendBuffer, 0, sendBuffer.Length); Task flushTask = stream.FlushAsync(cancellationToken); var recvBuffer = new byte[sendBuffer.Length]; await ReadToFillAsync(stream, recvBuffer, throwOnEmpty : true, cancellationToken).ConfigureAwait(false); // Realize any exceptions from writing to the stream. await Task.WhenAll(writeTask, flushTask).ConfigureAwait(false); for (int i = 0; i < ProtocolMagicNumber.Length; i++) { if (recvBuffer[i] != ProtocolMagicNumber[i]) { string message = "Protocol handshake mismatch."; if (options.TraceSource.Switch.ShouldTrace(TraceEventType.Critical)) { options.TraceSource.TraceEvent(TraceEventType.Critical, (int)TraceEventId.HandshakeFailed, message); } throw new MultiplexingProtocolException(message); } } bool?isOdd = null; for (int i = 0; i < randomSendBuffer.Length; i++) { byte sent = randomSendBuffer[i]; byte recv = recvBuffer[ProtocolMagicNumber.Length + i]; if (sent > recv) { isOdd = true; break; } else if (sent < recv) { isOdd = false; break; } } if (!isOdd.HasValue) { string message = "Unable to determine even/odd party."; if (options.TraceSource.Switch.ShouldTrace(TraceEventType.Critical)) { options.TraceSource.TraceEvent(TraceEventType.Critical, (int)TraceEventId.HandshakeFailed, message); } throw new MultiplexingProtocolException(message); } if (options.TraceSource.Switch.ShouldTrace(TraceEventType.Information)) { options.TraceSource.TraceEvent(TraceEventType.Information, (int)TraceEventId.HandshakeSuccessful, "Multiplexing protocol established successfully."); } return(new MultiplexingStream(stream, isOdd.Value, options)); }