Async Programming Model - Reloaded

This topic is about the asynchronous programming model, and how it is used when doing Networking.

 

Introduction to Asynchronous Programming

 

The DotNetFramework supports an asynchronous programming model that is uniform across all classes & features in the framework. What is cool about this model is that there is a uniform way of doing asynchronous operations across all the classes in the Framework.

 

The following links give an overview of Asynchronous programming in the .NETFramework.

 

https://msdn.microsoft.com/library/en-us/cpguide/html/cpovrasynchronousprogrammingoverview.asp?frame=true

 

https://msdn.microsoft.com/library/en-us/cpguide/html/cpconasynchronousfileio.asp?frame=true

 

Async programming in HttpWebRequest/HttpWebResponse

 

Let us proceed on to asynchronous programming in System.Net.HttpWebRequest class. The following code snippet demonstrates how to download data from a webserver using asynchronous programming pattern.

 


 

class Ep {

    static ManualResetEvent doneevent = New ManualResetEvent(False);

    static Byte [] Buffer = New Byte[1024];

    static MemoryStream DownloadedBytes = New MemoryStream();

    static void Main() {

        HttpWebRequest Req = (HttpWebRequest)WebRequest.Create("https://www.Contoso.Com/");

        Req.BeginGetResponse(new AsyncCallback(Respcallback), Req);

        doneevent.Waitone();

    }

    static void Respcallback(Iasyncresult Ar) {

        HttpWebRequest Req = (HttpWebRequest) Ar.AsyncState;

        HttpWebResponse Resp = Null;

        try {

            Resp = Req.EndGetResponse(Ar);

            Stream Respstream = Resp.GetResponseStream();

            Respstream.BeginRead(Buffer, 0, Buffer.Length, new AsyncCallback(Readcallback), Respstream);

        } catch(Exception E) {

            Console.Writeline(E);

            RespStream.Close();

            doneevent.Set();

        }

    }

    static void ReadCallback(IasyncResult Ar) {

  Stream Rs = (Stream)Ar.AsyncState;

        int Read = -1;

        try {

                Read = Rs.EndRead(Ar);

        } catch(Exception E) {

                Console.WriteLine(E);

                rs.Close();

                doneevent.Set();

                return;

        }

        if(Read > 0) {

            // Write To Memorystream

            DownloadedBytes.Write(Buffer,0,Read);

            try {

                rs.BeginRead(Buffer,0,Buffer.Length, new AsyncCallback(Readcallback), Rs);

            } catch(Exception E) {

                    Console.WriteLine(E);

                    doneevent.Set();

            }

        } else

        if( Read == 0) {

            // No More Data To Read.

            // Close The Response Stream To Free Up The Connection

            rs.Close();

            doneevent.Set();

        }

    }

}

 


 

The above code is the standard asynchronous pattern as applied to the HttpWebRequest/Response classes.

 

However this code could be improved. The most important thing to note here is that we are always calling an asynchronous Read method on the Response Stream returned by the response object. Sometimes, this might be overkill, because the data that we are trying to read is already present in the buffers of the networking stack on the machine. When data is already present to be read, the Read operation will complete very quickly. So, we can avoid some overhead with issuing asynchronous calls if we are smarter about when we issue them. If we know that data is already present to be read, we do a synchronous Read, otherwise we do an asynchronous Read.

 

Well, you might ask: How do I know that there is already data available to be read? The answer is to look at the members of the IAsyncResult structure which is passed to the callback delegate. This interface contains a property called “CompletedSynchronously”, which indicated if the async operation completed synchronously or not. We can utilize this property to decide if we should do a synchronous Read or an asynchronous Read.

 

Here is the modified code for the ReadCallback, with this change.

 


 

    static void ReadCallback(IAsyncResult Ar) {

        Stream Rs = (Stream)Ar.AsyncState;

        int Read = -1;

        try {

                Read = Rs.EndRead(Ar);

        } catch(Exception E) {

                Console.WriteLine(E);

                rs.Close();

                doneevent.Set();

                return;

        }

        if(Read > 0) {

            // Write To Memorystream

            DownloadedBytes.Write(Buffer,0,Read);

            try {

                if(rs.CompletedSynchronously) {

                    Read = rs.Read(Buffer,0,Buffer.Length);

                    while(Read > 0) {

                        DownloadedBytes.Write(Buffer,0,Read);

                        Read = rs.Read(Buffer,0,Buffer.Length);

  }

                    rs.Close();

                } else {

                    rs.BeginRead(Buffer,0,Buffer.Length, new AsyncCallback(ReadCallback), rs);

                }

            } catch(Exception E) {

                    Console.WriteLine(E);

                    rs.Close();

                    doneevent.Set();

            }

        } else

        if( Read == 0) {

            // No More Data To Read.

            // Close The Response Stream To Free Up The Connection

            rs.Close();

            doneevent.Set();

        }

    }


 

 

With this minor rewrite, we have now accounted for the Read completing synchronously. By doing this, we are preventing the underlying framework from taking an extra thread ( it needs to take an extra thread because the thread on which the async callback is scheduled to be called is different from the thread on which the BeginRead was issued).