ASP.NET, WCF, and Asynchronous Programming
Jeffrey Richter told me once that a CPU was a terrible thing to waste. I don't know if he was the one to coin the term - in fact, I think he told me someone else coined that term. Regardless of where it came from, the principle behind that phrase is solid. All too often, developers do I/O synchronously, thereby wasting CPU time, and making their applications less scalable and responsive. Terrible.
Often the excuse is that one expects the I/O to happen quickly. One seldom (if ever) can predict how fast any I/O is going to take, so this is a non-starter. Never do synchronous I/O. Never ever. Read Jeff Richter's CLR Via C# for an eloquent explanation of why.
This premise surfaces in WCF programming from within an ASP.NET application. Consider this scenario:
A Page_Load event handler needs to send a message to a web service and do something with the result.
Simply put, the ASP.NET application needs to do I/O. If the web service call is synchronous, the thread that is executing the Page_Load event (and the web service call) blocks until the web service call returns. That thread cannot be used to process other requests while waiting, and the web server will soon return 503 errors. Since the web service call takes a long time (try not to quibble that it is usually fast), you are wasting threads and CPU time. Terrible.
If, however, you use ASP.NET Async pages and an asynchronous call to the web service, you are not wasting the CPU. Very good. For more info on the async capabilities of ASP.NET 2.0, see Jeff Prosise's most excellent article: https://msdn.microsoft.com/msdnmag/issues/05/10/WickedCode/. With WCF, you would do something like:
1 public partial class _Default : System.Web.UI.Page
2 {
3 IService1 proxy;
4 ChannelFactory<IService1> factory;
5
6 protected void Page_Load(object sender, EventArgs e)
7 {
8 WSHttpBinding binding = new WSHttpBinding();
9 EndpointAddress address = new EndpointAddress("https://localhost:8080/Service1");
10
11 // you could also reuse a proxy...
12 factory = new ChannelFactory<IService1>(binding, address);
13
14 this.AddOnPreRenderCompleteAsync(
15 new BeginEventHandler(BeginAsyncOperation),
16 new EndEventHandler(EndAsyncOperation)
17 );
18 }
19
20
21 IAsyncResult BeginAsyncOperation(Object sender, EventArgs e,
22 AsyncCallback cb, Object state)
23 {
24 proxy = factory.CreateChannel();
25 return proxy.BeginGetData(TextBox1.Text, cb, state);
26 }
27
28 void EndAsyncOperation(IAsyncResult ar)
29 {
30 String output = proxy.EndGetData(ar);
31 Label2.Text = output;
32 }
33 }
Line 24 is where is I/O starts. BeginGetData returns an IAsyncResult and the thread goes into back into the thread pool. When the result comes back, the EndAsyncOperation method is called, and the proxy's end method can be called.
The model isn't hard, and it greatly improves the scalability of your ASP.NET application. On top of that, it doesn't waste the CPU...
The full code sample is here
Comments
Anonymous
October 19, 2007
PingBack from http://myghillie.info/1969/12/31/aspnet-wcf-and-asynchronous-programming/Anonymous
November 08, 2007
This is one good way to make efficient usage of your CPU. Read this information at Justin Smith's blogAnonymous
November 09, 2007
Alternatively, you can replace the AddOnPreRenderCompleteAsync with RegisterAsyncTask. From the Wicked Code article: "RegisterAsyncTask has four advantages over AddOnPreRenderCompleteAsync. First, in addition to Begin and End methods, RegisterAsyncTask lets you register a timeout method that's called if an asynchronous operation takes too long to complete. ... The second advantage is that you can call RegisterAsyncTask several times in one request to register several async operations. ... Third, you can use RegisterAsyncTask's fourth parameter to pass state to your Begin methods. Finally, RegisterAsyncTask flows impersonation, culture, and HttpContext.Current to the End and Timeout methods."