예제 #1
0
        /// <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);
        }
예제 #2
0
        /// <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));
        }