//Listing 12.9  Producer/consumer using TPL Dataflow
        public static async Task CompressAndEncrypt(
            Stream streamSource, Stream streamDestination,
            int maxDegreeOfParallelism = 4,
            long chunkSize             = 1048576, CancellationTokenSource cts = null)
        {
            cts = cts ?? new CancellationTokenSource();  //#A

            var compressorOptions = new ExecutionDataflowBlockOptions
            {
                MaxDegreeOfParallelism = maxDegreeOfParallelism,
                BoundedCapacity        = 20,
                CancellationToken      = cts.Token
            }; //#B

            var inputBuffer = new BufferBlock <CompressingDetails>(
                new DataflowBlockOptions
            {
                CancellationToken = cts.Token,
                BoundedCapacity   = 20
            });                         //#B

            var compressor = new TransformBlock <CompressingDetails, CompressedDetails>(
                async details =>
            {
                var compressedData = await IOUtils.Compress(details.Bytes);     //#C
                return(details.ToCompressedDetails(compressedData));            //#D
            }, compressorOptions);

            var encryptor = new TransformBlock <CompressedDetails, EncryptDetails>(
                async details =>
            {
                byte[] data       = IOUtils.CombineByteArrays(details.CompressedDataSize, details.ChunkSize, details.Bytes); //#E
                var encryptedData = await IOUtils.Encrypt(data);                                                             //#F
                return(details.ToEncryptDetails(encryptedData));                                                             //#D
            }, compressorOptions);

            var asOrderedAgent = Agent.Start((new Dictionary <int, EncryptDetails>(), 0),
                                             async((Dictionary <int, EncryptDetails>, int)state, EncryptDetails msg) =>
            {     //#G
                (Dictionary <int, EncryptDetails> details, int lastIndexProc) = state;
                details.Add(msg.Sequence, msg);
                while (details.ContainsKey(lastIndexProc + 1))
                {
                    msg = details[lastIndexProc + 1];
                    await streamDestination.WriteAsync(msg.EncryptedDataSize, 0, msg.EncryptedDataSize.Length);
                    await streamDestination.WriteAsync(msg.Bytes, 0, msg.Bytes.Length); //#H
                    lastIndexProc = msg.Sequence;
                    details.Remove(lastIndexProc);                                      //#I
                }
                return(details, lastIndexProc);
            }, cts);