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/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).
Comments
Anonymous
October 10, 2004
The comment has been removedAnonymous
October 11, 2004
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.Anonymous
October 21, 2004
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 / www.bartelink.comAnonymous
October 15, 2006
Now that DirectPlay has been depreciated any programmer that used Networking in there application areAnonymous
May 09, 2008
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,