



DataServiceQuery versus CloudTableQuery







Query Execution Methods


DataServiceQuery methods

CloudTableQuery methods

Execute Execute
BeginExecute BeginExecuteSegmented
EndExecute EndExecuteSegmented







token = null
   call Begin/EndExecuteSegmented with token
      while HasMoreResults
           call GetNext to retrieve additional results 
set token = ContinuationToken until token is null



Updating Azure@home


AsyncPageData class


protected class AsyncPageData
    public ResultContinuation ContinuationToken = null;
    public Boolean QueryResultsComplete = false; 




   1:  protected void RetrieveCompletedUnits()
   2:  {
   3:      if (!pageData.QueryResultsComplete)
   4:      {
   5:          // select enough rows to fill a page of the GridView
   6:          // GridView.PageSize < 1000 or UI paradigm will fail
   7:          Int32 maxRows = GridViewCompleted.PageSize;
   9:          // add one if first page, to force a page 2 indicator
  10:          if (pageData.ContinuationToken == null)
  11:              maxRows++;
  13:          // set up query
  14:          var qry = (from w in ctx.WorkUnits
  15:                      where w.Progress == 100
  16:                      select w).Take(maxRows).AsTableServiceQuery<WorkUnit>();
  18:          // use an event to synchronize

19: using (System.Threading.ManualResetEvent evt =
new System.Threading.ManualResetEvent(false))

  20:          {
  21:              // asynchronously fetch next page of data 
  22:              qry.BeginExecuteSegmented(pageData.ContinuationToken, (ar) =>
  23:                  {
  24:                      var response = (ar.AsyncState as CloudTableQuery<WorkUnit>)
  26:                      // add first segment of data
  27:                      pageData.CompletedList.AddRange(
  28:                          from wu in response.Results
  29:                          select new CompletedWorkUnit(wu));
  31:                      // continue fetching segments to complete page
  32:                      while (response.HasMoreResults)
  33:                      {
  34:                          response = response.GetNext();
  35:                          pageData.CompletedList.AddRange(
  36:                              from wu in response.Results
  37:                              select new CompletedWorkUnit(wu));
  38:                      }
  40:                      // set continuation token for next page request
  41:                      pageData.ContinuationToken = response.ContinuationToken;
  42:                      evt.Set();
  43:                  }
  44:                  , qry
  45:              );
  47:              // wait until async retrieval is complete 
  48:              evt.WaitOne();
  49:          }
  51:          // end of data reached if there's no continuation token
  52:          pageData.QueryResultsComplete = pageData.ContinuationToken == null;
  53:      }
  54:  }

The first 16 lines are nearly identical; the only substantive modification is the casting of the query in Line 16 to a CloudTableQuery (from the default of DataServiceQuery).

From there, though, it diverges considerably!  Let’s take a look at it in sections:

  • Lines 19 – 20:  Since we’re planning to make an asynchronous call to retrieve the data while the ASP.NET page lifecycle is underway, we need to block until we have the data back, and here I’m using a event to handle that.  RetrieveCompletedUnits blocks at Line 48 until the asynchronous callback has completed and set the event in Line 42.
  • Lines 22 – 45 technically comprise one line of code.  The BeginExecuteSegmented call accepts two parameters:

    • a continuation token marking where the next page starts (or null for the very first call), and
    • a callback, here a lambda function, that should execute when the asynchronous retrieval is complete.

Within the callback, the original query from the AsyncState (cf. Line 44) is reconstituted to obtain the response (Line 24).
From there, it’s fairly straightforward: Lines 27 - 29 take the data returned in the response, massage it from a WorkUnit to a CompletedWorkUnit, and add it to the collection of completed work units being stored in the session state object, pageData

Depending on the page size (and other circumstances), that initial set of results may not be complete; for instance, a “page size” of 1200, would return at most 1000 entities in that first batch of response.Results, so Line 27 would see only those 1000 rows.   The response reference (a ResultSegment) though realizes it still needs to get 200 more rows to meet the request, so HasMoreResults is set to indicate that. 

Retrieving those additional rows to fulfill the request is the job of the loop in Lines 32 – 38.  Once the ResultSegment indicates it has no more results (i.e., HasMoreResults is false), processing is complete.  To unblock the main thread and let RetrieveCompletedUnits continue, the event is set in Line 42.

  • Line 48 is where the main thread blocks until the event is set (in Line 42, by the asynchronous callback specified for BeginExecuteSegmented).
  • As results are being fetched, the ContinuationToken in the ResultSegment is getting updated appropriately and stored in the session variable, pageData, on Line 41
  • In the last line of this routine, Line 52, a boolean is set indicating that all of the data has been fetched from Azure table storage and is currently in memory.  Subsequent paging requests will traverse the in-memory cache to save storage transactions (granted, at the sacrifice of data currency).

Now this certainly works, but we can do better. 

The ASP.NET Page Lifecycle Revisited





ASP.NET Asynchronous Page Lifecycle





Implementing ASP.NET Asynchronous Pages




  4. allowing you to queue up multiple operations.


Registering Asynchronous Tasks




In the Page_Load code, for instance,  

  27:          cloudClient.CreateTableIfNotExist("workunit");
  28:          if (cloudClient.DoesTableExist("workunit"))
  29:          {
  30:              Page.RegisterAsyncTask(new PageAsyncTask(this.BeginRetrieveInProgressUnits, 
this.EndRetrieveInProgressUnits, this.OnTimeout, null, true));
  31:              Page.RegisterAsyncTask(new PageAsyncTask(this.BeginRetrieveCompletedUnits,  
this.EndRetrieveCompletedUnits, this.OnTimeout, null, true));
  32:          }
  33:          else
  34:          {
  35:              System.Diagnostics.Trace.TraceError("
Unable to create 'workunit' table in Azure storage"
  36:          }

Lines 30 and 31 show the registration of two new PageAsyncTasks.  One task handles the retrieval of the in-progress units, and the other handles the retrieval of the paginated, completed units.   Each task specifies a complementary Begin/End method pair of delegates to implement the asynchronous invocation pattern.

Begin Retrieval Event Handler Implementation for In-Progress Work Units

1: protected IAsyncResult BeginRetrieveInProgressUnits(object sender, EventArgs e,
AsyncCallback cb, object state)

   2:  {
   3:      var qry = (from w in ctx.WorkUnits
   4:                  where w.Progress < 100
   5:                  select w).AsTableServiceQuery<WorkUnit>();
   6:      return qry.BeginExecuteSegmented(cb, qry);
   7:  }

The number of in-progress units is bounded by the number of worker role instances (a maximum of 19 for typical Azure accounts) so no paging logic is implemented here.  The query is set up in Lines 3 - 5, just as it was for the synchronous implementation discussed in the previous post.   The only difference here is casting the query to a CloudTableQuery via the AsTableServiceQuery extension method.  Doing so provides access to additional methods, including BeginExecuteSegmented which fires off the actual asynchronous query to Azure table storage.

End Retrieval Event Handler Implementation for In-Progress Work Units

   1:  protected void EndRetrieveInProgressUnits(IAsyncResult ar)
   2:  {
   3:      var qry = ar.AsyncState as CloudTableQuery<WorkUnit>;
   4:      ResultSegment<WorkUnit> segment = qry.EndExecuteSegmented(ar);
   6:      // continue fetching all segments
   7:      pageData.InProgressList.AddRange(segment.Results);
   8:      while (segment.HasMoreResults)
   9:      {
  10:          segment = segment.GetNext();
  11:          pageData.InProgressList.AddRange(segment.Results);
  12:      }
  13:  }

When the results from the BeginExecuteSegmented invocation are ready, the end retrieval handler kicks in.  In Line 3, the original query is reconstituted from the state object passed to BeginExecuteSegmented and the first segment of results is accessed in Line 4.

As mentioned earlier, each ResultSegment includes an IEnumerable<T> collection of Results.  Those results though may not be complete.  Due to the 1000-entity limitation and internal partitioning of data, additional requests – via GetNext – may be required to return all of the data.  The boolean flag, HasMoreResults, indicates whether additional invocations are indeed required.  Lines 7 - 10 above handle this iteration, accumulating all of the results into a collection stored as part of the session state in pageData.

A ResultSegment also includes a ContinuationToken property marking the current position in the partition and row space; that value is important for paging scenarios as we’ll see next.

Begin Retrieval Event Handler Implementation for Completed Work Units

   1:  protected IAsyncResult BeginRetrieveCompletedUnits(object sender, EventArgs e, 
                                                          AsyncCallback cb, object state)
   2:  {
   3:      // select enough rows to fill a page of the GridView
   4:      // GridView.PageSize < 1000 or UI paradigm will fail
   5:      Int32 maxRows = GridViewCompleted.PageSize;
   7:      // add one if first page, to force a page 2 indicator
   8:      if (pageData.ContinuationToken == null)
   9:          maxRows++;
  11:      // set up query
  12:      var qry = (from w in ctx.WorkUnits
  13:                  where w.Progress == 100
  16:      // execute the query (with continuation token, if present)
  17:      if (pageData.ContinuationToken == null)
  18:          return qry.BeginExecuteSegmented(cb, qry);
  19:      else
  20:          return qry.BeginExecuteSegmented(pageData.ContinuationToken, cb, qry);
  21:  }

Much of the code above is similar to that of the synchronous implementation described in the previous post.   A CloudTableQuery is constructed in Lines 12 - 14, and then executed conditionally in Lines 17 - 20

Since this implementation accommodates paginating the display of completed work units, each page of GridView data corresponds to one invocation of BeginRetrieveCompletedUnits.  If it’s the first page, it’s a fresh query and there’s no continuation token, so the statement on Line 18 is executed.  For subsequent pages, the query needs to return results beginning where the previous page left off.  That’s where the continuation token comes in, so it’s passed in as a parameter in Line 20.

End Retrieval Event Handler implementation for Completed Work Units

   1:  protected void EndRetrieveCompletedUnits(IAsyncResult ar)
   2:  {
   3:      var qry = ar.AsyncState as CloudTableQuery<WorkUnit>;
   6:      // continue fetching segments to complete page
   7:      pageData.CompletedList.AddRange(
   8:          from wu in segment.Results
   9:          select new CompletedWorkUnit(wu)
  10:      );
  12:      while (segment.HasMoreResults)
  13:      {
  14:          segment = segment.GetNext();
  15:          pageData.CompletedList.AddRange(
  16:              from wu in segment.Results
  17:              select new CompletedWorkUnit(wu)
  18:          );
  19:      }
  21:      // set continuation token for next page request
  22:      pageData.ContinuationToken = segment.ContinuationToken;
  23:      pageData.QueryResultsComplete = pageData.ContinuationToken == null;
  24:  }

The pattern for retrieving result segments is identical to what we just saw for in-progress units: cycle through invocations of GetNext until HasMoreResults is set to false. and add the results to the in-memory collection pageData.CompletedList.   To keep track of where we are for subsequent page requests, the ContinuationToken is saved to session state in Line 22, and in Line 23 a flag is set to indicate that all results can now be served from the session cache.

Keep in mind there’s no transaction isolation in Azure table storage.  As you’re paging through data and new records have been added, you’ll see those ‘new’ records as you loop through the result segments, even if that data was not there when you initially issued the query.  Likewise, in the paging implementation described here, if you back up through pages, only the session cache is consulted, so data that was added since the page was first retrieved will not be seen until the entire web page is refreshed.

Timeout Event Handler implementation

One key benefit of using PageAsyncTask is being able to specify what happens if the tasks don’t complete within a reasonable amount of time – that time being expressed in the AsyncTimeout property of the @ Page directive, or defaulting to 45 second if not explicitly set. 

I opted for a pretty simplistic, but far from subtle, implementation here – the entire page background will be a bright red if the page generation is not completed within the allotted time.  You’d probably want something a bit less garish, but I went this route to keep non-essential changes to the original status.aspx page to a minimum.

public void OnTimeout(IAsyncResult ar)
    if (!this.ClientScript.IsClientScriptBlockRegistered(this.GetType(), "changeColor"))
        ClientScript.RegisterClientScriptBlock(this.GetType(), "changeColor",
            "<script> = \"#FF0000\"</script>");


Wrapping it up


Asynchronous Pages in ASP.NET 2.0, Jeff Prosise
ASP.NET Thread Usage on IIS 7.0 and 6.0, Thomas Marquardt
Multi-threading in ASP.NET, Robert Williams
Improve scalability in ASP.NET MVC using Asynchronous requests, Steve Sanderson
