Async Pages API
Chapter 5 of Programming Microsoft ASP.NET 2.0 Applications--Advanced Topics is tentatively titled "Building Richer Pages" and discusses three topics that can deserve a (short) chapter themselves--async pages, custom $-expressions, and page parser filters. These are apparently unrelated but used together offer you an unprecedented power as far as the runtime behavior of the page is concerned. You can make the page work async, expand your macros at parse time, build your own list of safe controls (like SharePoint does).
When I started on async pages, guess what, the first thing I did was looking into the excellent Jeff's article on MSDN Magazine and a number of posts from Fritz. It went pretty good and smooth until I tackled the RegisterAsyncTask method on the Page class.
In brief, to build an async page you have two steps to accomplish:
-
Add Async=true to your @Page directive
-
Register the async task(s) to execute
#2 can be done in either of two (nonexclusive) ways: using AddOnPreRenderCompleteAsync or RegisterAsyncTask. Both are new methods on the Page class. The former adds a pair of Begin/End async event handlers to the PreRenderComplete event. The latter requires you to pack the same pair of handlers (and more actually) in a wrapper class--PageAsyncTask--to be passed as an argument to RegisterAsyncTask. The tasks execute in between PreRender and PreRenderComplete on distinct threads--typically, the Begin thread is an ASP.NET worker thread and the End thread comes from completion thread pool.
Functionally speaking you get pages working exactly in the same way. Is there any difference? And why two distinct APIs? Here are some thoughts and findings:
- RegisterAsyncTask is a kind of independent API for executing tasks async from within an ASP.NET page. Unlike AddOnPreRenderCompleteAsync, it doesn't strictly require Async=true, though
- RegisterAsyncTask works also with Async=false (at least in Beta 2). The original thread that started the Begin phase is blocked until all operations are complete. If you use tracepoints (coolest feature ever of VS2005 that I found out about only yesterday) you see that the thread of Page_Load is the same of Page_Unload.
- For obvious scalability reasons, you might typically want to use RegisterAsyncTask in conjunction with async=true. However, the API is somewhat independent from async pages
- AddOnPreRenderCompleteAsync targets a multicast delegate so you can repeatedly call it to register multiple tasks. All these tasks execute sequentially to guarantee that the page renders only when all tasks are complete. Using tracepoints you see Begin/End Begin/End Begin/End sequences for each registered task. The thread on End triggers the next Begin. There’s no attempt of getting some parallelism
- If you want to obtain parallel execution with AddOnPreRenderCompleteAsync you must write your own IAsyncResult—which is boring but not hard (also thanks to a couple of posts from Dmitri). You use one Begin/End pair and fire internally all required async tasks.
- For one single async task, using RegisterAsyncTask (with Async=true) or AddOnPreRenderCompleteAsync is the same
- For multiple simultaneous tasks, you either use multiple calls to RegisterAsyncTask (with Async=true) or one call to AddOnPreRenderCompleteAsync with a custom IAsyncResult object to track the termination of all tasks.
- Using RegisterAsyncTask (with Async=true) makes your life considerably easier and results in simpler code to write
Using RegisterAsyncTask has also some other plusses:
- The End thread enjoys a richer context. It has impersonation info, intrinsics, HTTP context. For example, if you registered the task using AddOnPreRenderCompleteAsync, you can't rely on integrated security to access a database in the End handler. Likewise, you can't use HttpContext.Current to trace or access intrinsics
- Timeout support. You can define a timeout handler which runs if any of the tasks is not complete after a number of seconds (45 by default)
- Possibility of parallel execution (parallel execution is not the default, though)
- Much simpler (and I’d say cleaner) programming model for multiple tasks
- You can use the ExecuteRegisteredAsyncTasks method (on Page) to start tasks on demand. For example, in a postback without waiting for the unwind point between PreRender and PreRenderComplete
The bottom line?
- Use AddOnPreRenderCompleteAsync for one task, opt for RegisterAsyncTask for multiple tasks
PS: I'm aware that a lot of details about async pages are missing here (i.e., what the heck the unwind point is?). For an overview (and more) read the Jeff's article on MSDN Magazine.