I had a web application that generously used async/await keywords. The application encountered a slow response possibly due to a pending backend database call. I attached a debugger to the application. There was, however, nothing running in the process (see Figure 1), which was expected due to the calls being async and the waiting threads were no longer active.
It is frustrating seeing no code running when debugging applications using asynchronous methods. What can I do?
Figure 1 “Nothing” is running in the process. The 'Location' column is empty.
Introducing TPLEventListenerModule
Andrew Stasyuk has a good post (https://msdn.microsoft.com/en-us/magazine/JJ891052.aspx) introducing a way to get the call stacks (or causality chains) of async tasks. I figured I could leverage this method in my web application.
In a nutshell, the .NET Framework defines an event source, TplEtwProvider, which fires events that allow you to track Task lifetime. In .NET 4.5 and above, it is possible to subscribe to the event source to get the events. Here is what I do. I create an ASP.Net HttpModule, TPLEventListenerModule, which contains an event listener listening to TPL (Task Parallel Library) events. The listener captures two kinds of TPL event, TaskWaitBegin and TaskWaitEnd. Upon capturing an event, the listener stores the Task’s information -- such as, the Id of the task, the StackTrace where the event happens, the created time of the event, the type of the event, and so on -- in memory. The module ensures the listener initialized during BeginRequest event of requests. And I create an HttpHandler for viewing the stored information. The task information can also be accessed via APIs or be found in a dump of the web application.
The solution of the module is attached to this blog post. Here are some more details about the module.
1. The module is an ASP.Net HttpModule. To use it I just need to register the module in my web application’s web.config. The section to be added in web.config looks like this,
<system.webServer> … <modules> <add name="TPLEventListenerModule" type="TaskEventListenerModule.TPLEventListenerModule, TaskEventListenerModule"/>
</modules> … </system.webServer>
2. The captured event info is stored in the following two ways:
Each HttpContext instance has a SingleRequestTaskStore instance that stores the current active Task’s info for the corresponding request.
Figure 2 A SingleRequestTaskStore instance is stored in the HttpContext's Items bag.
Each HttpContext instance has a DebuggingInfoList containing all events for the corresponding request.
Figure 3 A DebuggingInfoList contains all Task Begin/End events for the corresponding request.
3. The module puts/removes every request’s HttpContext into/from a ConcurrentItemStore in the request’s BeginRequest/EndRequest event. I can access the stored HttpContext via the CurrentContextViewer.
Figure 4 The Contexts property contains HttpContexts for all active requests.
4. I wrote an HttpHandler, ViewAsyncTaskInfoHandler. The handler can give me a view of all active requests and all async Task info if there is any. To enable the handler in my application, in web.config I added a section like this,
<system.webServer> … <handlers> <add type="TaskEventListenerModule.ViewAsyncTaskInfoHandler, TaskEventListenerModule" name="ViewAsyncTaskInfoHandler"
resourceType="Unspecified" path="vat.axd" verb="POST,GET,HEAD"></add>
</handlers> … </system.webServer>
Trying it Out
The attached solution includes a demo project. The default page has two hyperlinks, “Async Page” and “View Async Tasks”. Start the application and browse the default page, you can try clicking “Async Page” a few times and then click “View Async Tasks”, and “View Async Tasks” page will show you a list of requests running async tasks.
With the TPLEventListenerModule in place, I can now know what async task my request is awaiting even though I do not see any active thread. If you ever have the same problem, get a copy of the module into your project today.