Using PageAsyncTask to improve the performance of your website


Once in a while I like to get back to “old stuff”. It allows me to look once again at the things I’ve already done but then from another perspective: between now and then I’ve done a few projects, I’ve read a couple articles/books and spoken to some people. So it’s not surprising that quite often I find some new insights about the subjects I thought were closed. One of such topics was using the PageAsyncTask class to improve the performance of your website.

PageAsyncTask 101

PageAsyncTask class allows you to register your code for asynchronous processing. By leveraging it you can run your code in parallel decreasing the processing time and therefore making your pages load faster.

Imagine the following case: you build a Web Part that makes three calls to some heavy method. It takes 3 seconds to run that method. In an old-fashioned way it would take at least 9 seconds to render the Web Part on a page:

public class MyWebPart : WebPart
{
  protected override void CreateChildControls()
  {
    LongRunningMethod(); // 1st call
    LongRunningMethod(); // 2nd call
    LongRunningMethod(); // 3rd call
    // 9 seconds later...
  }

  void LongRunningMethod()
  {
    Thread.Sleep(3000);
  }
}

Now imagine doing the same using the PageAsyncTask. The basic idea is that it allows you to run your code in parallel. That means that it would take only 3 seconds instead of 9 to render the same Web Part!

Andrew Connell has described using the PagesAsyncTask class for developing custom Web Parts in his book. If you’re a SharePoint developer and are creating custom Web Parts it’s definitely something you should know of.

So what’s new about the PageAsyncTask?

Is it really running in parallel?

By now I have seen only a couple of examples of how PageAsyncTask is being used inside custom Web Parts. All these examples were similar to the one I showed you above: a Web Part is making several calls to some long running (not necessarily the same) methods. By making these calls using the PageAsyncTask the code is being executed in parallel and you win some precious rendering time.

Almost everyone I asked about why should we use PageAsyncTask said that the most important reason is that you never know what kind of controls and Web Parts and how many of them are present of a particular page and should therefore be a good citizen and run your code as smooth as possible. I made an assumption: so if I use PageAsyncTask in my custom Web Part and put multiple instances of it on one page, they all will run in parallel as if you had only one? Not really…

Consider the following example: an RSS Viewer Web Part using the PageAsyncTask class:

RssViewerWebPart.cs

public class RssViewerWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
  [WebBrowsable(true), WebDescription("Url of the RSS feed"), WebDisplayName("RSS url"), Personalizable(PersonalizationScope.Shared)]
  public string RssUrl { get; set; }

  GetRssAsyncTask getRssAsyncTask;
  Literal feedsLiteral;

  protected override void CreateChildControls()
  {
    if (!String.IsNullOrEmpty(RssUrl))
    {
      getRssAsyncTask = new GetRssAsyncTask(RssUrl, null);
      PageAsyncTask getRssPageAsyncTask = new PageAsyncTask(getRssAsyncTask.OnBegin, getRssAsyncTask.OnEnd, getRssAsyncTask.OnTimeout, "GetRssAsyncTask", true);
      Page.RegisterAsyncTask(getRssPageAsyncTask);
      Page.ExecuteRegisteredAsyncTasks();

      if (getRssAsyncTask.Result != null && getRssAsyncTask.Result.Count > 0)
      {
        StringBuilder feeds = new StringBuilder();

        foreach (XmlNode post in getRssAsyncTask.Result)
        {
          feeds.AppendFormat("<a href=\"{0}\">{1}</a><br />{2}", post.SelectSingleNode("link").InnerText, post.SelectSingleNode("title").InnerText, Environment.NewLine);
        }

        feedsLiteral.Text = feeds.ToString();
      }
    }
  }
}

GetRssAsyncTask.cs

class GetRssAsyncTask : AsyncTask<XmlNodeList>
{
  public string RssUrl { get; set; }

  public GetRssAsyncTask(string rssUrl)
  {
    if (String.IsNullOrEmpty(rssUrl))
    {
        throw new ArgumentNullException(rssUrl);
    }

    RssUrl = rssUrl;
  }

  public override void Execute()
  {
    XmlDocument rssFeed = new XmlDocument();
    rssFeed.Load(RssUrl);

    Result = rssFeed.DocumentElement.SelectNodes("channel/item");
  }
}

AsyncTask.cs

public abstract class AsyncTask<TResult>
{
  public TResult Result { get; protected set; }
  delegate void AsyncTaskDelegate();
  private AsyncTaskDelegate task;

  public virtual IAsyncResult OnBegin(object sender, EventArgs e, AsyncCallback callback, object data)
  {
    task = new AsyncTaskDelegate(Execute);
    return task.BeginInvoke(callback, data);
  }

  public virtual void OnEnd(IAsyncResult result)
  {
    task.EndInvoke(result);
  }

  public virtual void OnTimeout(IAsyncResult result)
  {
    Result = default(TResult);
  }

  public abstract void Execute();
}

The Web Part has one property: the URL of the RSS feed you want to display. Using an asynchronous call the Web Part calls the Execute method of the GetRssAsyncTask class which is responsible for retrieving the feeds and processing the posts. While you could implement the asynchronous task in a single class I have created an abstract base class which covers the common functionality for every asynchronous task.

If you put an instance of that Web Part on a page it works correctly:

RSS Viewer Web Part 

If you put three instances of that Web Part it’s still okay:

Multiple RSS Viewer Web Parts on one page 

But let’s have a look at the Trace information: it took my development machine around 1,5 second to retrieve and render the feeds. While it doesn’t seem that much it makes me wonder: is it really working in parallel?

To confirm that things were working as they should I have created synchronous version of the RSS Viewer Web Part:

public class SyncRssViewerWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
  [WebBrowsable(true), WebDescription("Url of the RSS feed"), WebDisplayName("RSS url"), Personalizable(PersonalizationScope.Shared)]
  public string RssUrl { get; set; }

  protected override void CreateChildControls()
  {
    if (!String.IsNullOrEmpty(RssUrl))
    {
      XmlDocument rssFeed = new XmlDocument();
      rssFeed.Load(RssUrl);

      XmlNodeList result = rssFeed.DocumentElement.SelectNodes("channel/item");
      if (result != null && result.Count > 0)
      {
        foreach (XmlNode post in result)
        {
          HyperLink link = new HyperLink() { NavigateUrl = post.SelectSingleNode("link").InnerText, Text = post.SelectSingleNode("title").InnerText };
          Controls.Add(link);
          Controls.Add(new LiteralControl("<br/>"));
        }
      }
    }
  }
}

I added three instances of this Web Part on another page and took a look at the Trace information. Surprisingly the rendering time was almost the same! Does that PageAsyncTask even work?

It’s all in the details

Looking at the implementation of PageAsyncTask in my RSS Viewer Web Part one thing took my attention: I was correctly creating and registering a new PageAsyncTask for each long running operation but right after that I was calling the Page.ExecuteRegisteredAsyncTasks method. While it works if you had multiple tasks registered within the same Web Part, it causes serial execution of code across multiple Web Parts.

From MSDN I’ve found out that the ExecuteRegisteredAsyncTasks method is being called automatically just before the PreRenderComplete event. So theoretically by not calling it myself I could register all the long running methods as asynchronous tasks and let the Page itself execute them in parallel.

To do that I have modified the code as follows:

RssViewerWebPart.cs

public class RssViewerWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
  [WebBrowsable(true), WebDescription("Url of the RSS feed"), WebDisplayName("RSS url"), Personalizable(PersonalizationScope.Shared)]
  public string RssUrl { get; set; }

  GetRssAsyncTask getRssAsyncTask;
  Literal feedsLiteral;

  protected override void CreateChildControls()
  {
    if (!String.IsNullOrEmpty(RssUrl))
    {
      getRssAsyncTask = new GetRssAsyncTask(RssUrl, ShowRssFeeds);
      PageAsyncTask getRssPageAsyncTask = new PageAsyncTask(getRssAsyncTask.OnBegin, getRssAsyncTask.OnEnd, getRssAsyncTask.OnTimeout, "GetRssAsyncTask", true);
      Page.RegisterAsyncTask(getRssPageAsyncTask);

      feedsLiteral = new Literal();
      Controls.Add(feedsLiteral);
    }
  }

  void ShowRssFeeds()
  {
    if (getRssAsyncTask.Result != null && getRssAsyncTask.Result.Count > 0)
    {
      StringBuilder feeds = new StringBuilder();

      foreach (XmlNode post in getRssAsyncTask.Result)
      {
        feeds.AppendFormat("<a href=\"{0}\">{1}</a><br />{2}", post.SelectSingleNode("link").InnerText, post.SelectSingleNode("title").InnerText, Environment.NewLine);
      }

      feedsLiteral.Text = feeds.ToString();
    }
  }
}

Notice that the results are not being rendered in the CreateChildControls method anymore and are processed using the ShowRssFeeds delegate instead.

GetRssAsyncTask.cs

class GetRssAsyncTask : AsyncTask<XmlNodeList>
{
  public string RssUrl { get; set; }

  public GetRssAsyncTask(string rssUrl, TaskFinished taskFinishedCallBack) : base(taskFinishedCallBack)
  {
    if (String.IsNullOrEmpty(rssUrl))
    {
        throw new ArgumentNullException(rssUrl);
    }

    RssUrl = rssUrl;
  }

  public override void Execute()
  {
    XmlDocument rssFeed = new XmlDocument();
    rssFeed.Load(RssUrl);

    Result = rssFeed.DocumentElement.SelectNodes("channel/item");
  }
}

The constructor of the GetRssAsyncTask has been extended to support passing the TaskFinished delegate.

AsyncTask.cs

public abstract class AsyncTask<TResult>
{
  public TResult Result { get; protected set; }
  delegate void AsyncTaskDelegate();
  private AsyncTaskDelegate task;
  public delegate void TaskFinished();
  private TaskFinished taskFinished;

  public AsyncTask()
  {

  }

  public AsyncTask(TaskFinished taskFinishedHandler)
  {
    taskFinished = taskFinishedHandler;
  }

  public virtual IAsyncResult OnBegin(object sender, EventArgs e, AsyncCallback callback, object data)
  {
    task = new AsyncTaskDelegate(Execute);
    return task.BeginInvoke(callback, data);
  }

  public virtual void OnEnd(IAsyncResult result)
  {
    if (taskFinished != null)
    {
      taskFinished.Invoke();
    }

    task.EndInvoke(result);
  }

  public virtual void OnTimeout(IAsyncResult result)
  {
    Result = default(TResult);
  }

  public abstract void Execute();
}

The class has been extended to support executing a method after the task has been ended.

Once again I took a look at the Trace information for the asynchronous RSS Viewer Web Part: 0,5 second!

So should we or should we not call the Page.ExecuteRegisteredAsyncTasks method ourselves? On one hand it allows us to process the results of the task execution immediately in the CreateChildControls method. This is a great thing because it allows us to dynamically create controls based on the results of the task.

On the other hand if you call Page.ExecuteRegisteredAsyncTasks yourself, you loose the parallel execution across multiple Web Parts. While the code inside the Web Part will be executed in parallel, all the different Web Parts won’t. ASP.NET will render all the Web Parts serially.

Asynchronous code execution: is it worth the effort?

Asynchronous programming is not as straight forward as doing things in the old-fashioned way. But just because it’s different it doesn’t mean you shouldn’t do it. Depending on your scenario it might give you some serious performance benefits. The more complex methods you’re running the more performance you gain by executing the code in an asynchronous manner. And while PageAsyncTask is not an answer to all performance-related challenges it’s definitely something you should be familiar with.

Technorati Tags: SharePoint,SharePoint 2007,MOSS 2007,WSS 3.0

Others found also helpful: