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.

 

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

 

http://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("http://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).

 

 

 

 

 

Comments (5)
  1. Hi

    I’d be delighted to see the example extended to include BeginGetResponseStream. I for one got caught out by the fact that GetResponseStream is a blocking call (admittedly bceause I didnt RTFM) as it never makes it into the examples.

    Had I been aware of this I might have mandated up front that the client end of the system accept GET instead of POST requests 🙁

    Regards,

    –Ruben

    Ruben Bartelink / +353 86 885 2399 / http://www.bartelink.com

  2. Feroze Daud says:

    Ruben,

    While the example can easily be extended to GetResponseStream(), it actully doesnt make sense to do so. The reason, is that you can only call GetResponseStream() only once. Whereas on the streams returned by GetResponseStream() & GetRequestStream() respectively, you will have to call Read() and Write() more than once.

  3. Hi Feroze,
    <br>
    <br>Despite doing a quick check before posting, I still managed to mangle the question. The references to BeginGetResponseStream and GetResponseStream should instead refer to BeginGetRequestStream and GetRequestStream.
    <br>
    <br>Looking at it again, introducing this (i.e. doing a POST instead of a GET certainlty removes the introductory aspect of the blog entry so I can appreciate it doesnt belong specifically in this post.
    <br>
    <br>Thanks,
    <br>
    <br>–Ruben
    <br>Ruben Bartelink / +353 86 885 2399 / http://www.bartelink.com

  4. Now that DirectPlay has been depreciated any programmer that used Networking in there application are

  5. Now that DirectPlay has been depreciated any programmer that used Networking in there application are going to have to build the networking layer on top of the standard networking objects. Note that these resources could also be used in standard Applications,

Comments are closed.

Skip to main content