Top 5 ways to debug async/await and multi-threaded code in Visual Studio

Debugging multi-threaded code with Visual Studio - Ozcode

Not all debug sessions were created equal; some are easier while others require remote connecting to a specific machine only on Wednesday at midnight. I’m sure you have some horror stories about elusive bugs and endless debug sessions. I did notice that when asked about the top ten hardest things to debug, most developers would name “multi-threading” as one of the top three. I get it, debugging code that runs in a non-deterministic order with so many different things running at the same time is tough. There are a few nifty tools, including some lesser-known Visual Studio features that can help you reduce (if not completely eliminate) the pain of debugging multi-threaded code.

Sample code

For most of this post, I’ll use the same simple example – tree traversal.

Consider the following class:

class Tree
{
	public Tree(Tree left, Tree right)
	{
		Left = left;
		Right = right;
	}

	public Tree Left { get; }
	public Tree Right { get; }
}

For the sake of this example, let’s say we want to count the number of nodes in the tree – the hard way:

private static int WalkTree(Tree tree)	
        if (tree == null)
	{
		return 0;
	}

	var valLeft = 0;
	var valRight = 0;

	var tasks = new Task[2];
	tasks[0] = Task.Factory.StartNew(() => valLeft = CalculateLeft(tree));
	tasks[1] = Task.Factory.StartNew(() => valRight = CalculateRight(tree));

	Task.WaitAll(tasks);

	var result = valLeft + valRight + 1;

	return result;
}
private static int WalkTree(Tree tree)
{
	if (tree == null)
	{
		return 0;
	}

	var valLeft = 0;
	var valRight = 0;

	var tasks = new Task[2];
	tasks[0] = Task.Factory.StartNew(() => valLeft = CalculateLeft(tree));
	tasks[1] = Task.Factory.StartNew(() => valRight = CalculateRight(tree));

	Task.WaitAll(tasks);

	var result = valLeft + valRight + 1;

	return result;
}

So let’s dive in. I’ll place a breakpoint on the return at the end of the method.

Find root cause - Ozcode

Parallel Stacks

The first must-have tool that comes out of the box with Visual Studio is under the debug menu (Debug -> Windows -> Parallel Tasks).

Once it stops at the breakpoint the first time, I see the following view:

Parallel Stacks Window

In the right hands, this window is amazing. We can jump between threads and see exactly what’s executing either by Threads or Tasks. If you want to learn more, there’s an excellent walkthrough on MSDN .

Parallel Watch

Any developer who has ever pressed F5 knows (and loves) the various Watch windows – there’s also Autos, Locals, and if that’s not enough, four more Watch windows where you can go nuts!

Unfortunately, once you start trying to understand values of variables over multiple threads or tasks, you find yourself frantically skipping between threads checking the watch window each time. That is both confusing and frustrating. There is a hidden gem under the Debug menu called Parallel Watch which gives a glance at the same variables across multiple threads:

Parallel Watch Window

First, we get the Thread and Task Ids followed by recursion depth, and we can see the various values throughout the threads/tasks in the system. Since it’s not that interesting yet, I’ll put a conditional breakpoint to stop once result is greater than 50 and see what happens:

parallelwatch2

We can almost see the tree being traversed. Jumping one more F5 ahead will give me those values:

parallelwatch3

Combined with the Parallel Stack window and a bit of tinkering, we can quickly find out just about anything we want to learn about our code’s parallel execution path:

parallelstacks2

Tasks Window

Did you know that there’s a debug window dedicated to tasks? Ever opened it? (hint: Debug -> Windows -> Tasks).

No big deal – right? We had a similar Threads window since the beginning of time. Well, that Tasks window can help us understand the existing tasks in the system – for example, the sample project looks something like this:

Tasks Window

See those colored icons? They tell me what the state of the task is. And yes, they can also show when one task is Deadlocked!

taskswindowdeadlock

Trace

All of the features above are great, but they all suffer from one major flaw – they stop the execution of your code, and here’s the problem with multi-threaded code. Messing with the way the threads execute can create a completely different execution order, which is unfortunate if it makes the bug you’re trying to pin-down, disappear – only to reappear on a production system, late at night, or during the weekend.

And so, you need a less intrusive way of gathering more information about the issue you’re investigating. If you try to use the same debugging techniques you use day in, day out, set a breakpoint, and hit F10 repeatedly – that won’t work! What will invariably happen is that you’ll suddenly hit a breakpoint on a different thread, hit F10 a few more times, and you’ll get an exception on yet another thread. It quickly becomes a problem of trying to juggle too many balls in your head all at once.

The go-to solution for those kinds of problems is to add more logging. The problem is that it means you have to stop debugging, add some more logging, hit F5, reproduce the steps, rinse and repeat again and again if you need more information.

Instead, Visual Studio provides Tracepoints, which are like breakpoints that log information instead of stopping. While using Tracepoints will cause a performance hit, from my experience, most of the time, it’s good enough to catch the bug. Using Tracepoint gets even better with Ozcode Visual Studio Extension.

Adding a new tracepoint using Ozcode is simple: First, you need to put a “regular” breakpoint, and once you stop, open the quick watch window and use the magic wand to add a Tracepoint.

createtracepoint

Ozcode’s Create Tracepoint window will open, and you can add text and any object or value in the current context to the Tracepoint.

tracepointwindow

Continue running and check Ozcode’s Tracepoints window at the end of the debug run for details and analysis.

tracepoints

To learn more about Tracepoints, check our feature page or GitHub examples.

Show All Instances

The last Ozcode feature that targets multi-threaded debugging is Show All Instances. One of the issues with multi-threading is that the data you’re trying to get tends to be spread across several executing threads. For that, we have the cool feature that shows all the instances (or property values on those instances) throughout the system.

It’s simple to use. Navigate to an object and use the point at the class you care about. Use the Quick Actions button to choose “Show All Instances”. That’s it.

showallinstances

To navigate to the class definition, click on the quick actions for each property.

showallgenderinallinstances

Now you can grab any value from anywhere – regardless of the current execution context, and you’re even able to do a full text search through every instance!

Conclusion

Debugging can be painful, and multi-threading can be hard. As you saw, collecting information in order to understand and fix the bug can be a lot easier than you think. It’s just a matter of knowing your tools and picking the right tool for each job.


Ozcode Visual Studio Extension

Tracepoints: Easily diagnose complicated multi-threaded bugs by analyzing a linear log of execution.

Show All Instances: Effortlessly find specific objects in memory according to specific properties and their values to quickly understand why they’re still there.

Time travel to see how your code will execute before you step through it.

Heads-up display gives you powerful visualizations so you can instantly understand what’s happening in your code.

Dror Helper

Comments

Keep your code
in good shape!

Follow the latest news in the .NET debugging industry

Ready to Dive into Your Prod Code?

Easy debugging with full time-travel data

Share on facebook
Share on twitter

Recent Posts

Follow Us

Join Ozcode YouTube Channel

Let’s start debugging, it’s free!

Keep your code in good shape!

Follow the latest news in the .NET debugging industry
Ozcode Logo

This website uses cookies to ensure you get the best experience on our website.