XpiritO XpiritO - 1 month ago 15
C# Question

.NET Asynchronous stream read/write

I have been trying to solve this "Concurrent Programming" exam exercise (in C#):


Knowing that
Stream
class contains
int Read(byte[] buffer, int offset, int size)
and
void Write(byte[] buffer, int offset, int size)
methods, implement in C# the
NetToFile
method that copies all data received from
NetworkStream net
instance to the
FileStream file
instance. To do the transfer, use asynchronous reads and synchronous writes, avoiding one thread to be blocked during read operations. The transfer ends when the
net
read operation returns value 0. To simplify, it is not necessary to support controlled cancel of the operation.


void NetToFile(NetworkStream net, FileStream file);


I've been trying to solve this exercise, but I'm struggling with a question related with the question itself. But first, here is my code:

public static void NetToFile(NetworkStream net, FileStream file) {
byte[] buffer = new byte[4096]; // buffer with 4 kB dimension
int offset = 0; // read/write offset
int nBytesRead = 0; // number of bytes read on each cycle

IAsyncResult ar;
do {
// read partial content of net (asynchronously)
ar = net.BeginRead(buffer,offset,buffer.Length,null,null);
// wait until read is completed
ar.AsyncWaitHandle.WaitOne();
// get number of bytes read on each cycle
nBytesRead = net.EndRead(ar);

// write partial content to file (synchronously)
fs.Write(buffer,offset,nBytesRead);
// update offset
offset += nBytesRead;
}
while( nBytesRead > 0);
}


The question I have is that, in the question statement, is said:


To do the transfer, use asynchronous
reads and synchronous writes, avoiding
one thread to be blocked during read
operations


I'm not really sure if my solution accomplishes what is wanted in this exercise, because I'm using
AsyncWaitHandle.WaitOne()
to wait until the asynchronous read completes.

On the other side, I'm not really figuring out what is meant to be a "non-blocking" solution in this scenario, as the
FileStream
write is meant to be made synchronously... and to do that, I have to wait until
NetworkStream
read completes to proceed with the
FileStream
writing, isn't it?

Can you, please, help me out with this? Thanks in advance for your colaboration.




[ EDIT 1 ] Using callback solution

Ok, if I understood what Mitchel Sellers and willvv replied (thank you guys), I've been counseled to use a callback method to turn this into a "non-blocking" solution. Here is my code, then...

byte[] buffer; // buffer

public static void NetToFile(NetworkStream net, FileStream file) {
// buffer with same dimension as file stream data
buffer = new byte[file.Length];
//start asynchronous read
net.BeginRead(buffer,0,buffer.Length,OnEndRead,net);
}

//asynchronous callback
static void OnEndRead(IAsyncResult ar) {
//NetworkStream retrieve
NetworkStream net = (NetworkStream) ar.IAsyncState;
//get number of bytes read
int nBytesRead = net.EndRead(ar);

//write content to file
//... and now, how do I write to FileStream instance without
//having its reference??
//fs.Write(buffer,0,nBytesRead);
}


As you may have noticed, I'm stuck on the callback method, as I don't have a reference to the
FileStream
instance where I want to invoke the "Write(...)" method.

Additionally, this is not a thread-safe solution, as the
byte[]
field is exposed and may be shared among concurrent
NetToFile
invocations. I don't know how to solve this problem without exposing this
byte[]
field in the outer-scope... and I'm almost sure it may not be exposed this way.

I don't want to use a lambda or anonymous method solution, because that's not in the curriculum of "Concurrent Programing" course.

Answer

You are going to need to use the callback from the NetStream read to handle this. And frankly it might be easier to wrap the copying logic into its own class so that you can maintain the instance of the active Streams.

This is how I'd approach it (not tested):

public class Assignment1
{
    public static void NetToFile(NetworkStream net, FileStream file) 
    {
        var copier = new AsyncStreamCopier(net, file);
        copier.Start();
    }

    public static void NetToFile_Option2(NetworkStream net, FileStream file) 
    {
        var completedEvent = new ManualResetEvent(false);

        // copy as usual but listen for completion
        var copier = new AsyncStreamCopier(net, file);
        copier.Completed += (s, e) => completedEvent.Set();
        copier.Start();

        completedEvent.WaitOne();
    }

    /// <summary>
    /// The Async Copier class reads the input Stream Async and writes Synchronously
    /// </summary>
    public class AsyncStreamCopier
    {
        public event EventHandler Completed;

        private readonly Stream input;
        private readonly Stream output;

        private byte[] buffer = new byte[4096];

        public AsyncStreamCopier(Stream input, Stream output)
        {
            this.input = input;
            this.output = output;
        }

        public void Start()
        {
            GetNextChunk();
        }

        private void GetNextChunk()
        {
            input.BeginRead(buffer, 0, buffer.Length, InputReadComplete, null);
        }

        private void InputReadComplete(IAsyncResult ar)
        {
            // input read asynchronously completed
            int bytesRead = input.EndRead(ar);

            if (bytesRead == 0)
            {
                RaiseCompleted();
                return;
            }

            // write synchronously
            output.Write(buffer, 0, bytesRead);

            // get next
            GetNextChunk();
        }

        private void RaiseCompleted()
        {
            if (Completed != null)
            {
                Completed(this, EventArgs.Empty);
            }
        }
    }
}