Invoking multiple WCF service asynchronously and in parallel, from an aspx page.

Invoking web service or any I/O activity which is time/resource intensive, can get lot of help from asynchronous processing, simply because the request thread invokes all registered async tasks & goes back to the thread pool & is free to attend next request.
Later when async processing is all done, another thread from the pool takes charge, to finish that pending request processing & pushing the response to client.

1. First a little about PageAsyncTask class

Starting .Net 2.0, PageAsyncTask is a class which enables the page, process asynchronously multiple tasks either in sequence or in parallel. This multiple task execution, happens in a single request coming on the server. There is nothing AJAX involved here.The response is sent back to the client after all the async tasks are done OR if the page has timed out. If we go into little details, when the request hits ASP.Net, just after PreRender page processing stage, the main request thread starts all the registered async tasks and immediately goes back to the thread-pool.So this request thread is free from the ‘long running task’ wait. It can serve other incoming requests and hence the web server does not gets choked under heavy load. Now when the async tasks are all done OR the page times out, the response would be sent back to the client.One thing to care, would be — when the page times out — the timeout duration as mentioned in the asyncTimeout property is applicable to “Page” & not time spent on individual async tasks. Also when the page times out, and is rendered to the client, the async process(es) in the background still continues to execute, untill done or failed. All the async task’s result/output are ready in PreRenderComplete stage of Page processing cycle.
On this handler, we can use the output or data obtained from async tasks, populate/bind any form elements.

Some more related information as obtained from MSDN.
PageAsyncTask.ExecuteInParallel Property
true if the task should be processed in parallel with other tasks; otherwise, false
The ExecuteInParallel property is set in the constructor. When multiple tasks are registered in a page and the ExecuteInParallel property is set to true, then those tasks are processed concurrently. However, if the ExecuteInParallel property is set to false, then those tasks are processed sequentially. For example, if a page contained two asynchronous tasks that each took 5 seconds to complete and ExecuteInParallel is set to true, both tasks will complete in 5 seconds. If ExecuteInParallel is set to false for these same two tasks, then the first task will complete in 5 seconds and the second task will complete 5 seconds after the completion of the first task.

PageAsyncTask.State Property
Gets an object that represents the state of the task.The State property is set in the constructor and cannot be modified during execution of the asynchronous task. You can differentiate asynchronous tasks in a page by assigning unique string values to their respective State properties

PageAsyncTask.TimeoutHandler Property
Gets the method to call when the task does not complete successfully within the time-out period.

An EndEventHandler delegate that represents the method to call when the task does not complete successfully within the time-out period

2. Adding an WCF service to my website (Step-by-Step walkthrough)
In VS2010, create a new website.

Click ‘Add New Item’ and add a new WCF Service.

img1

Open the Service1.cs and add a new Service Method.

img2

img3

Right click on Service1.svc and say “View in Browser”. Copy the link from the browser address bar.

img4

Next we will add service reference to our website. Under Advanced tab, make sure, you check the “Generate asynchronous operations” and say ok.

img6

Now coming back to the aspx page. Please add the attribute “Async=true” in the .aspx file , which basically tells the runtime that the page will be processed asyncronously.

img7

So now we successfully added a service reference & VS2010 created the proxy.

3. Calling multiple tasks from different WCF Service asynchronously , in parallel.

img8

In this example below, my web page calls 2 WCF service methods asynchronously & both the ServiceMethod runs parallel.
If you notice, in the results image , all the async tasks are processed at the same point of time. Now some code

<%@ Page Language="C#" Async="true" AutoEventWireup="true" Trace="false" CodeFile="CallMultipleWebMethodsFromSameService.aspx.cs" Inherits="CallMultipleWebMethodsFromSameService" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
    </div>
    <asp:Label ID="Label1" runat="server" Text=""></asp:Label>
    <br /><br />
    <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Call WCF Service1" />
    </form>
</body>
</html>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Diagnostics;

public partial class CallMultipleWebMethodsFromSameService : System.Web.UI.Page
{
    private string _LabelOutput = "";
    ServiceReference1.Service1Client MyClient1 = null;
    ServiceReference2.Service2Client MyClient2 = null;

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
    }

    protected void Page_Load(object sender, EventArgs e)
    {

    }

    protected void Button1_Click(object sender, EventArgs e)
    {
        //If I change the last parameter to false, then my async tasks would run sequentially, one after another
        PageAsyncTask task1 = new PageAsyncTask(BeginGetAsyncData, EndGetAsyncData, TimeoutGetAsyncData, "1", true);
        Page.RegisterAsyncTask(task1);

        PageAsyncTask task2 = new PageAsyncTask(BeginGetAsyncData, EndGetAsyncData, TimeoutGetAsyncData, "2", true);
        Page.RegisterAsyncTask(task2);
    }

    IAsyncResult BeginGetAsyncData(object src, EventArgs args, AsyncCallback cb, object state)
    {
        if (state.ToString() == "1")
        {
            MyClient1 = new ServiceReference1.Service1Client();
            return MyClient1.BeginMyService1Method1(cb, state);
        }
        else
        {
            MyClient2 = new ServiceReference2.Service2Client();
            return MyClient2.BeginMyService2Method2(cb, state);
        }
       
    }

    void EndGetAsyncData(IAsyncResult ar)
    {
        string returnedValue = "";
        string returnedValue2 = "";
        if ((string)ar.AsyncState == "1")
        {
            returnedValue = MyClient1.EndMyService1Method1(ar);
            lock (_LabelOutput)  //lock to avoid async thread contention
            {
                _LabelOutput += "<br>From Service1: " + returnedValue + " "+ DateTime.Now.ToString();
            }
        }
        else
        {
            returnedValue2 = MyClient2.EndMyService2Method2(ar);
            lock (_LabelOutput)
            {
                _LabelOutput += "<br>From Service2: " + returnedValue2 + " " + DateTime.Now.ToString();
            }
        }
        
    }

    void TimeoutGetAsyncData(IAsyncResult ar)
    {
        Response.Write("<b>Request timed out</b><br />");
    }

    protected override void OnPreRenderComplete(EventArgs e)
    {
        base.OnPreRenderComplete(e);
        if (IsPostBack)
        {
            Label1.Text = _LabelOutput;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
[ServiceContract]
public interface IService1
{
    [OperationContract]
    void DoWork();

    [OperationContract]
    string MyService1Method1();

    [OperationContract]
    string MyService1Method2();
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;

// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1 : IService1
{
    public void DoWork()
    {
    }

    public string MyService1Method1()
    {
        System.Threading.Thread.Sleep(6000);
        return "Hello World1 " + DateTime.Now.ToString();
    }

    public string MyService1Method2()
    {
        System.Threading.Thread.Sleep(6000);
        return "Hello World2 " + DateTime.Now.ToString();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService2" in both code and config file together.
[ServiceContract]
public interface IService2
{
    [OperationContract]
    void DoWork();

    [OperationContract]
    string MyService2Method1();

    [OperationContract]
    string MyService2Method2();
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;

// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service2" in code, svc and config file together.

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service2 : IService2
{
    public void DoWork()
    {
    }

    public string MyService2Method1()
    {
        System.Threading.Thread.Sleep(6000);
        return "Hello World A " + DateTime.Now.ToString();
    }

    public string MyService2Method2()
    {
        System.Threading.Thread.Sleep(6000);
        return "Hello World B " + DateTime.Now.ToString();
    }
}
That's it. Hope that helps.

Reference:

http://msdn.microsoft.com/en-us/magazine/cc163725.aspx
http://www.pluralsight-training.net/community/blogs/mike/archive/2005/11/04/16213.aspx

 


 

Advertisements
This entry was posted in WCF. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s