0

I would like to read a stream and buffer it's output so that a consumer could read it before the producer has finish full reading of the stream. example, read from an Http stream and forward to another ... This using the .net4.5 TPL library. I found out the Nito AsyncCollection and below is what I wrote. but I am wondering if this is all correct because when I debug, even using a long string to test it and even when debuging from asp.net vnext pipeline, it does readfrom and write to in sequence... How could I be sure it has the correct behaviour in NON debug mode ?

           using Nito.AsyncEx;
    using System;
    using System.Collections.Concurrent;
    using System.IO;
    using System.Threading.Tasks;
    using System.Threading.Tasks.Dataflow;

    namespace Absyla.Core
    {
        public class PipeStream 
        {
            private AsyncCollection<byte[]> _chunks = new AsyncCollection<byte[]>(1);

            public async Task ReadFrom(Stream s)
            {
                try
                {
                    byte[] buffer;
                    int bytesRead = 0;
                    int totalBytesRead = 0;
                    do
                    {
                        long buffsize = Math.Min(s.Length - totalBytesRead, 1L);
                        buffer = new byte[buffsize];
                        bytesRead = await s.ReadAsync(buffer, 0, buffer.Length);

                        if (bytesRead > 0)
                        {
                            int readId = System.Environment.CurrentManagedThreadId;
                            await _chunks.AddAsync(buffer);
                            totalBytesRead += bytesRead;
                        }

                    }
                    while (bytesRead > 0);
                }
                finally
                {
                    _chunks.CompleteAdding();
                }
            }

            public async Task WriteTo(Stream s)
            {
                while (await _chunks.OutputAvailableAsync())
                {
                    int writeId = System.Environment.CurrentManagedThreadId;
                    byte[] buffer = await _chunks.TakeAsync();
                    await s.WriteAsync(buffer, 0, buffer.Length);
                }
            }
        }
    }

here is a test

    [Fact]
    public async Task ShouldBufferLittleString()
    {
        String testString = "test in";

        PipeStream ps = new PipeStream();
        MemoryStream ms2 = new MemoryStream();

        await ps.ReadFrom(testString.AsStream());
        await ps.WriteTo(ms2);

        string result = ms2.ReadFromStart();

        result.ShouldBe(testString);
    }

note : the AsStream() method is an extension that returns a stream object from a String.

As I found it simple, I wonder if this is really correct and if it really does the job I want...

UPDATE : changing the following:

private AsyncCollection<byte[]> _chunks = new AsyncCollection<byte[]>();

to

private AsyncCollection<byte[]> _chunks = new AsyncCollection<byte[]>(2);

made the throttling work. but my test blocks. and it does work when I do this:

public async Task ShouldBufferLittleString()
    {
        String testString = "test in";

        PipeStream ps = new PipeStream();
        MemoryStream ms2 = new MemoryStream();

        var readTask =  ps.ReadFrom(testString.AsStream());
        var writeTask = ps.WriteTo(ms2);

        await Task.WhenAll(readTask, writeTask);

        string result = ms2.ReadFromStart();

        result.ShouldBe(testString);
    }

but in my code I cannot always have reference to the read and write task as it can be called from different location in the code.

I tried to change to BufferBlock, but had the same issue.

UPDATE 2 : I changed the pipestream code a little to add throtling and an buffer of 1 just for test.

in the test, when I await immediately it blocks, but when I create the task and I await them after, the test passes.

 // this blocks:    
        await ps.ReadFrom(testString.AsStream());
        await ps.WriteTo(ms2);
//this passes
        var read = ps.ReadFrom(testString.AsStream());
        var write = ps.WriteTo(ms2);

        await read;
        await write;
Cedric Dumont
  • 1,009
  • 17
  • 38
  • I found out that what I neede is throttling. I can have that by putting the capacity in the AsyncCollection constructor. but my test blocks and if I use Task.waitAll it does not block. (I updated the question for this) – Cedric Dumont Apr 22 '15 at 16:04
  • I think using RX would give you more flexibility. – Andrei Tătar Apr 22 '15 at 16:18
  • Reactive Extensions, IObservable, etc. – Andrei Tătar Apr 22 '15 at 16:47
  • TPL Dataflow might also be good for this. – Cory Nelson Apr 22 '15 at 17:04
  • tpl dataflow is what i am using but my problem is that the readfrom method blocks the thread and i don t know why – Cedric Dumont Apr 22 '15 at 17:34
  • i think i have to await the consumer before the producer for this to work, cause the producer blocks because it reaches it s capacity and no consumer were registered yet. i have to try iy – Cedric Dumont Apr 22 '15 at 19:19
  • Check [Stephen Cleary's](http://blog.stephencleary.com/2012/11/async-producerconsumer-queue-using.html) blog post for an example of using throttling on BufferBlock or ActionBlock. Use `SendAsync` instead of `Post` to await for a throttled block to clear. Otherwise, check the return value of `Post` to see whether the data was rejected due to throttling – Panagiotis Kanavos Apr 24 '15 at 11:35
  • I don't quite understand what is it that you're asking. Could you make the question more focused? Specifically, it would be useful if you explained one situation where your code doesn't work including how it doesn't work and how do you want it to work. – svick Jul 19 '15 at 15:00

0 Answers0