internal static void ReadTargetBytes(ref PerfWorkerState state)
        {
            const int bufferSize = 8192;
            var       buffer = new byte[bufferSize];
            int       bytesRead, bytesToRead = bufferSize;

            var pacifierLevel = state.bytesLeft - PacifierOffset;

            while ((state.bytesLeft > 0)
#if !(NET20 || NET35)
                   && !state.token.IsCancellationRequested
#endif
                   )
            {
                if (state.bytesLeft < bufferSize)
                {
                    bytesToRead = bufferSize;
                }

                bytesRead        = state.stream.Read(buffer, 0, bytesToRead);
                state.bytesLeft -= bytesRead;

                if (state.bytesLeft <= pacifierLevel)
                {
                    Debug.WriteLine($"Reader {state.bytesLeft} bytes remaining");
                    pacifierLevel = state.bytesLeft - PacifierOffset;
                }

                if (bytesRead == 0)
                {
                    break;
                }
            }
        }
        public static void TestWrite(int size, Func <Stream, Stream> output, Action <Stream> outputClose = null)
        {
#if !(NET20 || NET35 || NET40)
            var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));
#endif
            var sw          = Stopwatch.StartNew();
            var writerState = new PerfWorkerState()
            {
                bytesLeft = size,
#if !(NET20 || NET35 || NET40)
                token = cts.Token,
#endif
                baseStream = new NullStream(),
                streamCtr  = output,
            };

            writerState.InitStream();
            WriteTargetBytes(ref writerState);

            writerState.DeinitStream();

            writerState.stream.Close();

            var elapsed  = sw.Elapsed;
            var testSize = size / ByteToMB;
            Console.WriteLine($"Time {elapsed:mm\\:ss\\.fff} throughput {testSize / elapsed.TotalSeconds:f2} MB/s (using test size: {testSize:f2} MB)");
        }
        internal static void WriteTargetBytes(ref PerfWorkerState state)
        {
            const int bufferSize   = 8192;
            var       buffer       = new byte[bufferSize];
            var       bytesToWrite = bufferSize;

            while (state.bytesLeft > 0
#if !(NET20 || NET35)
                   && !state.token.IsCancellationRequested
#endif
                   )
            {
                if (state.bytesLeft < bufferSize)
                {
                    bytesToWrite = bufferSize;
                }

                state.stream.Write(buffer, 0, bytesToWrite);
                state.bytesLeft -= bytesToWrite;
            }
        }
        public static void TestReadWrite(int size, Func <Stream, Stream> input, Func <Stream, Stream> output, Action <Stream> outputClose = null)
        {
#if !(NET20 || NET35 || NET40)
            var cts    = new CancellationTokenSource(TimeSpan.FromMinutes(1));
            var window = new WindowedStream(size, cts.Token);
#else
            var window = new WindowedStream(size);
#endif


            var readerState = new PerfWorkerState()
            {
                bytesLeft = size,
#if !(NET20 || NET35 || NET40)
                token = cts.Token,
#endif
                baseStream = window,
                streamCtr  = input,
            };

            var writerState = new PerfWorkerState()
            {
                bytesLeft = size,
#if !(NET20 || NET35 || NET40)
                token = cts.Token,
#endif
                baseStream = window,
                streamCtr  = output,
                streamCls  = outputClose
            };

            var reader = new Thread(stateObject =>
            {
                var state = (PerfWorkerState)stateObject;
                try
                {
                    // Run output stream constructor
                    state.InitStream();

                    // Main read loop
                    ReadTargetBytes(ref state);

#if !(NET20 || NET35)
                    if (!state.token.IsCancellationRequested)
#endif
                    {
                        Assert.IsFalse(state.baseStream.CanRead, "Base Stream should be closed");

                        // This shouldnt read any data but should read the footer
                        var buffer    = new byte[1];
                        var readBytes = state.stream.Read(buffer, 0, 1);
                        Assert.LessOrEqual(readBytes, 0, "Stream should be empty");
                    }

                    // Dispose of the input stream
                    state.stream.Close();
                }
                catch (Exception x)
                {
                    state.exception = x;
                }
            });

            var writer = new Thread(stateObject =>
            {
                var state = (PerfWorkerState)stateObject;
                try
                {
                    // Run input stream constructor
                    state.InitStream();

                    // Main write loop
                    WriteTargetBytes(ref state);

                    state.DeinitStream();

                    // Dispose of the input stream
                    state.stream.Close();
                }
                catch (Exception x)
                {
                    state.exception = x;
                }
            });

            var sw = Stopwatch.StartNew();

            writer.Name = "Writer";
            writer.Start(writerState);

            // Give the writer thread a couple of seconds to write headers
            Thread.Sleep(TimeSpan.FromSeconds(3));

            reader.Name = "Reader";
            reader.Start(readerState);

            bool      writerJoined = false, readerJoined = false;
            const int timeout = 100;

            while (!writerJoined && !readerJoined)
            {
                writerJoined = writer.Join(timeout);
                if (writerJoined && writerState.exception != null)
                {
#if !(NET20 || NET35 || NET40)
                    ExceptionDispatchInfo.Capture(writerState.exception).Throw();
#endif
                }

                readerJoined = reader.Join(timeout);
                if (readerJoined && readerState.exception != null)
                {
#if !(NET20 || NET35 || NET40)
                    ExceptionDispatchInfo.Capture(readerState.exception).Throw();
#endif
                }

#if !(NET20 || NET35 || NET40)
                if (cts.IsCancellationRequested)
                {
                    break;
                }
#endif
            }

            //Assert.IsTrue(writerJoined, "Timed out waiting for reader thread to join");
            //Assert.IsTrue(readerJoined, "Timed out waiting for writer thread to join");

#if !(NET20 || NET35 || NET40)
            Assert.IsFalse(cts.IsCancellationRequested, "Threads were cancelled before completing execution");
#endif

            var elapsed  = sw.Elapsed;
            var testSize = size / ByteToMB;
            Console.WriteLine($"Time {elapsed:mm\\:ss\\.fff} throughput {testSize / elapsed.TotalSeconds:f2} MB/s (using test size: {testSize:f2} MB)");
        }