Lesser-known Multi-threaded Debugging Support in Visual Studio 2010

Lesser-known Multi-threaded Debugging Support in Visual Studio 2010

Rate This
  • Comments 5

We’ve been very excited about the new debugging windows in Visual Studio 2010, namely Parallel Tasks and Parallel Stacks, as well as the newly revamped Threads window, and thus we’ve talked about them quite a bit. For an overview, you can read the MSDN Magazine article at http://msdn.microsoft.com/en-us/magazine/ee410778.aspx, and Daniel Moth has a larger collection of related materials in his blog post on Parallel Debugging. From all the coverage these windows have been getting, however, it would be easy to overlook many of the other niceties that have been added to Visual Studio 2010 around multithreaded debugging, and in particular related to the new parallelism support in .NET 4. With this post, I hope to bring some of those lesser-known but still quite useful features to light.

DebuggerDisplayAttribute

Wherever applicable, our new .NET 4 parallelism-related types sport DebuggerDisplay attributes in order to make it really easy to see important details about the relevant component at a glance . This, of course, exists for collections as you would expect, e.g.

clip_image001

However, it also works for other types. For example, if you hover over an instance of Task, you’ll see details like the Task’s action or function, its execution status, etc.:

clip_image002

The following parallelism-related types new to System, System.Threading, System.Threading.Tasks, and System.Collections.Concurrent in .NET 4 all have DebuggerDisplayAttribute applied to them to showcase useful data:

  • AggregateException
  • Barrier
  • BlockingCollection<T>
  • CancellationToken
  • ConcurrentBag<T>
  • ConcurrentDictionary<TKey,TValue>
  • ConcurrentQueue<T>
  • ConcurrentStack<T>
  • CountdownEvent
  • Lazy<T>
  • ManualResetEventSlim
  • ParallelLoopState
  • SemaphoreSlim
  • SpinLock
  • Task
  • Task<TResult>
  • TaskScheduler
  • ThreadLocal<T>

 

DebuggerTypeProxyAttribute

Similarly, wherever applicable, our new .NET 4 parallelism-related types utilize debugger type proxies in order to provide a concise and useful debugging view of the state of an instance. For example, the TaskScheduler type (which represents the scheduling infrastructure used to execute tasks) provides a type proxy that allows you to see all of the tasks that have been scheduled to that scheduler instance and are awaiting execution, e.g.

clip_image004

Task itself also has a debugger type proxy, so you could further expand one of these tasks to understand more details, or just view an individual task you might have a reference to, e.g.:

clip_image005

There are some cool tricks you can play with this as well.  For example, the Task type internally maintains information about what the current Task is; while this information isn’t exposed publicly, and while it could change in the future, for .NET 4 you can access this information through the debugger by accessing Task’s static InternalCurrent property, allowing you to learn about the current task executing:

image

Here’s a list of the new parallelism-related types that have a DebuggerTypeProxy attribute applied:

  • BlockingCollection<T>
  • ConcurrentBag<T>
  • ConcurrentDictionary<TKey,TValue>
  • ConcurrentQueue<T>
  • ConcurrentStack<T>
  • Lazy<T>
  • SpinLock
  • Task
  • Task<TResult>
  • TaskScheduler
  • ThreadLocal<T>

 

IntelliTrace Events

IntelliTrace is an extremely cool new feature of Visual Studio 2010, one you can learn more about in the MSDN Magazine article at http://msdn.microsoft.com/en-us/magazine/ee336126.aspx. Built-in to Visual Studio 2010 are several events specific to parallelism and/or that relate to some of these new .NET 4 types. You can see these events in the debugging options for IntelliTrace:

clip_image006

These events are not enabled by default, so if you want them to be tracked, you’ll need to enable them in the above options dialog, available through “Debugging | Options and Settings… | IntelliTrace | IntelliTrace Events”.

With these events selected, you’ll see the relevant events show up in the IntelliTrace events window:

clip_image008

Thread Slipping

When stopped at breakpoints, the debugger is able to use technology called “funcevals” to execute code in order to compute results, such as to show you data in windows like Watch and Locals, in tooltips, and so forth. Of course, this funceval is able to execute any arbitrary .NET code, including code that might take locks. When stopped at a breakpoint, the debugger has frozen all of the application’s threads, and then to run a funceval, it wakes up one thread and uses it to run the target function. If this function depends on another thread doing some work in order for the unfrozen thread to make forward progress, the system will deadlock, as the thread that needs to do the work is unable to do so.

The Visual Studio 2010 debugger introduces the concept of “thread slipping,” which the new parallelism types in .NET 4 take advantage of in key places. If the debugger is making a funceval into a particular piece of code we know is likely to result in this kind of negative situation, the Framework informs the debugger that it should stop what it’s doing and make sure the user really wants to continue. The user is then presented with an option that allows the debugger to wake up all of the processes’ threads until the target function completes, at which point the debugger will again freeze all of the threads.

We take advantage of this, for example, in Task<T>.Result:

clip_image010

Notice the little threads icon in the Watch window line for t3.Result:

clip_image011

By clicking that button, the debugger will wake up the system until t3.Result completes (or until a really long timeout expires):

clip_image012

This thread slipping functionality is used in several places throughout Parallel Extensions, including Lazy<T>, ThreadLocal<T>, Task<T>, and PLINQ. You can utilize it in your own types as well, by calling System.Diagnostics.Debugger.NotifyOfCrossThreadDependency() prior to the problematic operation.

Leave a Comment
  • Please add 7 and 5 and type the answer here:
  • Post
  • Thanks! Great info!

  • Correct me if I'm wrong, but as I recall, reading the Task.Exception property causes the exception to be flagged as "observed", which changes behavior down the line. Does the proxy for Task mark the exception as observed, or does the proxy have its own Exception property that bypasses the side effect? I.e., if I just hover over a task in the debugger, and expand it out to see its properties, am I changing my program's behavior, or did you design it so that doesn't change behavior? What if I drill into the Raw View?

  • Hi Joe-

    It's a great question.  There are a variety of types, e.g. Lazy<T>, where we do utilize debugger functionality to ensure that viewing the type in the debugger, hovering over properties, etc., doesn't change the app's behavior.  For example, if you have over a Lazy<T>'s Value property in the debugger, that will not cause the Lazy<T> to be initialized, nor will examining it through its type proxy, or its debugger display attribute.  For Task, we had a long debate about what to do here for Exception, and in the end we decided not to special case it, so if you view the exception in the debugger, we won't tear down the process.  The reasoning here is that we tear down the process for unobserved exceptions in order to make the developer aware that an exception went unhandled, but if the developer is viewing that information in the debugger, they're already aware.  Additionally, unlike Lazy<T> (and others, like ThreadLocal<T>) there's no real runtime behavior being modified that would result in differing results; this is an error path, and all of the application's exception handling code will still execute... the only thing that would change would be whether the TaskScheduler.UnobservedTaskScheduler event will fire prior to the app crashing, and the real purpose of that event is to allow a developer to log exceptions so they know about them and then potentially prevent the app from crashing, so again this doesn't affect such paths.  Feedback on this decision is welcome... it's the kind of thing we could change in the future if doing so is deemed important.

  • Very interesting. My initial reaction was to think that hovering over a Task in the debugger shouldn't result in a behavior change at runtime. But you still need to worry about things like the Watches window -- if you added a watch on the Exception property, then I think that would have to cause the side effect (since I don't think the Watches window uses proxies, so you'd have no way to avoid the side effect). So you're going to have side effects from debugging, it's just a question of how pervasive they'll be -- and, as you pointed out, how severe their side effects actually are.

    I do think that it's curious that you chose to make Exception a property, instead of a method, partly for these very reasons. Have you blogged about that design decision? I don't recall reading about it, but I might have missed it -- and I'm always curious about these kinds of decisions.

  • Hi Joe-

    Hovering over a Task in the debugger won't cause the exception to be observed as the exception information isn't shown in DebuggerDisplay attribute; you're correct, however, that if you expand the tooltip such that we then show the DebuggerTypeProxy for the Task that we'll then observe the exception because we're showing the Exception property.  Note that because this is a DebuggerTypeProxy and we have full control over the execution at this point, we could have chosen not to make this operation observe the exception (even if we show the exception information), but for better or worse, we did.  We did have a long debate about this behavior, whether to make it a  method or a property, and of course in the end decided on a property.  

    Your feedback is certainly appreciated and we'll definitely keep your thoughts in mind for the future (in particular because changing this debugging-related behavior isn't a breaking change and thus is something we could choose to modify in the future if doing so was important).

Page 1 of 1 (5 items)