September 4, 2012 06:52 by
Scott
Introduction
Asynchronously running code can improve the overall performance and responsiveness of your web application. In ASP.NET 4.5 web forms applications you can register asynchronous methods with the page framework. The ASP.NET page framework and .NET 4.5 asynchronous programming support then executes the operations in asynchronous fashion. This article shows how this can be done.
Example Scenario
Consider that you have a web application that needs to call two ASP.NET Web API services namely Customer and Product. These services return Customer and Product data from the Northwind database respectively. Now, assume that each of these services take 5 seconds to complete the data retrieval operation. If you use synchronous mode for calling these services then the total time taken will be 10 seconds. Because the execution will happen sequentially - first Customer service will complete and then Product service will complete.
On the other hand if you invoke these services in asynchronous fashion, the service operations won't block the caller thread. The Customer service will be invoked and control will be immediately returned to the caller. The caller thread will then proceed to invoke the Product service. So, two operations will be invoked in parallel. In this case the total time taken for completing both of the operations will be the time taken by the longest of the operations (5 seconds in this case).
Async, Await, Task and RegisterAsyncTask
Before developing web forms applications that execute asynchronous operations you need to understand a few basic terms involved in the process.
A task is an operation that is to be executed in asynchronous fashion. Such an operation is programmatically represented by the Task class from System.Threading.Tasks namespace.
When an asynchronous operation begins, the caller thread can continue its work further. However, the caller thread must wait at some point of time for the asynchronous operation to complete. The await keyword invokes an asynchronous operation and waits for it to complete.
The async modifier is applied to a method that is to be invoked asynchronously. Such an asynchronous method typically returns a Task object and has at least one await call inside it.
Just to understand how async, await and task are used at code level, consider the following piece of code:
public async Task<MyObject> MyMethodAsync()
{
MyObject data = await service.GetDataAsync();
//other operations on data go here
return data;
}
Here, method MyMethodAsync() is marked with async modifier. By convention, asynchronous method names end with "Async". The MyMethodAsync() returns MyObject wrapped inside a Task instance. Inside the method a remote service is invoked using GetDataAsync(). Since MyMethodAsync() needs to return data retrieved from the service, the await keyword is used to wait till the GetDataAsync() method returns. Once GetDataAsync() returns the execution is resumed and further code is executed. The data is finally returned to the caller.
NOTE:
For a detailed understanding of async, await and Task refer to MSDN dicumentation. Here, these terms are discussed only for giving a basic understanding of the respective keywords.
ASP.NET page framework provides a method - RegisterAsyncTask() - that registers an asynchronous task with the page framework. Tasks registered using the RegisterAsyncTask() method are invoked immediately after the PreRender event. The RegisterAsyncTask() method takes a parameter of type PageAsyncTask. The PageAsyncTask object wraps the information about an asynchronous task registered with a page. The following piece of code shows how they are used:
protected void Page_Load(object sender, EventArgs e)
{
PageAsyncTask task = new PageAsyncTask(MyMethod);
RegisterAsyncTask(task);
}
Asynchronous Solution
Now that you are familiar with the basic concepts involved in utilizing asynchronous operations in a web forms application, let's create a sample application that puts this knowledge to use.
Begin by creating two projects - an empty web forms application and an ASP.NET MVC4 Web API application.
Add an Entity Framework Data Model for the Customers and Products tables of the Northwind database. Place the EF data model inside the Models folder.
Add an Entity Framework Data Model for the Customers and Products tables
Add two ApiController classes to the Web API project and name them as CustomerController and ProductController.
Add two ApiController classes
Then add Get() methods to both the ApiController classes as shown below:
public class CustomerController : ApiController
{
public IEnumerable<Customer> Get()
{
Northwind db = new Northwind();
var data = from item in db.Customers
select item;
System.Threading.Thread.Sleep(5000);
return data;
}
}
public class ProductController : ApiController
{
public IEnumerable<Product> Get()
{
Northwind db = new Northwind();
var data = from item in db.Products
select item;
System.Threading.Thread.Sleep(5000);
return data;
}
}
The Get() method of the CustomerController class selects all the Customer records from the Customers table whereas the Get() method of the ProductController class selects all the Product records. For the sake of testing, a delay of 5 seconds is introduced in each Get() method. The Get() methods return an IEnumerable collection of Customer and Product objects respectively.
Now, go to the web forms project and open the code behind file of the default web form. Here, you will write a couple of private methods that invoke the Web API developed previously. These methods are shown below:
public async Task<List<Customer>> InvokeCustomerService()
{
using (HttpClient client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync("http://localhost
string json= (await response.Content.ReadAsStringAsync());
List<Customer> data = JsonConvert.DeserializeObject<List<Customer>>(json
return data;
}
}
public async Task<List<Product>> InvokeProductService()
{
using (HttpClient client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync("http://localhost
string json = (await response.Content.ReadAsStringAsync());
List<Product> data = JsonConvert.DeserializeObject<List<Product>>(json
return data;
}
}
The InvokeCustomerService() method invokes the Customer Web API whereas InvokeProductService() method invokes Product Web API. Both the methods essentially use an HttpClient to get data from the respective Web API. Notice that both the methods have async modifier and return a Task instance that wraps the actual return type (List<Customer> and List<Product> respectively). The GetAsync() method of the HttpClient object is an asynchronous method. Call to the GetAsync() is marked using the await keyword so that further statements are executed only when GetAsync() returns. The GetAsync() method accepts a URL of the respective Web API. Make sure to change the port number as per your development setup. The GetAsync() method returns an HttpResponseMessage object. The actual data is then retrieved using ReadAsStringAsync() method of the Content property. The ReadAsStringAsync() will return data as a JSON string. This JSON data is converted into a .NET generic List using DeserializeObject() method of the JsonConvert class. The JsonConvert class comes from the Json.NET open source componenet. You can download Json.NET here.
The InvokeCustomerService() and InvokeProductService() methods are called inside another private method GetDataFromServicesAsync() as shown below:
private async Task GetDataFromServicesAsync()
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
var task1 = InvokeCustomerService();
var task2 = InvokeProductService();
await Task.WhenAll(task1, task2);
List<Customer> data1 = task1.Result;
List<Product> data2 = task2.Result;
stopWatch.Stop();
Label2.Text = string.Format("<h2>Retrieved {0} customers and {1} products
data1.Count, data2.Count, stopWatch.Elapsed.TotalSeconds
}
As shown above, GetDataFromServicesAsync() is also marked as async and returns a Task instance. Inside, a StopWatch class from System.Diagnostics namespace is used to find the time taken by both of the operations to complete. InvokeCustomerService() and InvokeProductService() methods are then called. The returned Task instance is stored in task1 and task2 variables respectively. The WhenAll() method of Task class creates another Task that completes when all the specified tasks are complete. In this case it creates a Task that completes after complition of task1 and task2. Actual data returned by the respective Web API is retrieved using the Result property of the respective Task objects. The time taken to complete the operation is measured by the StopWatch and is displayed in a Label.
The next step is to register GetDataFromServicesAsync() with the page framework. This is done using the RegisterAsyncTask() method as shown below:
protected void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(GetDataFromServicesAsync));
}
As you can see, Page_Load event handler registers an asynchronous task using RegisterAsyncTask() method. The RegisterAsyncTask() method accepts an instance of PageAsyncTask. The PageAsyncTask instance in turn wraps the GetDataFromServicesAsync() method created earlier.
The final step is to set Async attribute of the @Page directive to true:
<%@ Page Async="true" Language="C#" CodeBehind="WebForm1.aspx.cs" ... %>
The Aync attribute of the @Page directive indicates that this web form will be executed in asynchronous fashion. Web forms that use RegisterAsyncTask() method must set the Async attribute to true, otherwise an exception is raised at runtime.
This completes the application and you can test it by running the web forms application. The following figure shows a sample run of the web form:
A sample run of the web form
Though the code doesn't show the synchronous execution of the Web API operations, for the sake of better understanding the above figure shows time taken for synchronous as well as asynchronous execution. Recollect that both the Get() methods sleep for 5 seconds and hence the synchronous execution takes approximately 10 seconds. However, the asynchronous execution takes approximately 5 seconds. As you can see the asynchronous operation improves the overall performance of the application.
Summary
Using async and await keywords you can create operations that run asynchronously. Such asynchronous tasks can be registered with the page framework using RegisterAsyncTask() method. Registered tasks run immediately after the PreRender event of the web form. Asynchronous operations can improve the overall performance and user responsiveness of a web application.