Freigeben über


The Art of Debugging – A Developer’s Best Friend – Lesson 4 – Debugging Threads - Repost

Threading Primer

Threading is a complex topic. Parallelism is another word used to describe the concept of running code in parallel. Understanding threading is an important first step to following this tutorial.

Unless very carefully designed, multithreaded programs tend to be unpredictable, hard and to test, and hard to get right.

Waiting for something to happen is something that threads need to do frequently. Whenever you read user input or access a disk, your thread must wait. Because the disk drive and the user are millions of times slower than the processor, waiting for something that applications need to do is something we need to be very good at. Specifically, we need a way to wake up certain threads when other threads exit. Since the operating system is responsible for thread shutdown, it seems reasonable that the operating system would be able to tell other threads when a given thread shuts down. We will do this programmatically with the examples below.

There are many different types of threads:

Type

Synchronizes access to code regions.

Mutex

Same as Lock/Monitor but can work system-wide.

Interlocked

Use when performing atomic arithmetic operations and comparisons. Avoid dirty read scenarios.

Read—Writer

Useful when there are many read-only threads but fewer writer threads.

Semaphores

Controls the number of threads that can access a resource at any point in time.

EventWaitHandle

Threads participate in synchronization by signaling each other.

In order to make sense of some of the debugging features, I am providing a brief tutorial on threading topics. Nearly all computers today come equipped with either multi-core or multi CPU configuration. More than ever is important to architect applications that take advantage of threading approaches. The biggest challenge to writing code that utilizes threads is getting them debugged and working correctly. As a field engineer I noticed that most performance issues both on the server and on the client related to broken threads. Sometimes threads get deadlocked other times they spin endlessly waiting for a resource to free and sometimes that resource never gets freed.

EventWaitHandle() & EventResetMode.AutoReset

I encourage you to download the project below and run it to see exactly how it works. This sample covers the AutoReset method of context switching between threads.

Click here for the download

image

EventWaitHandle() & EventResetMode.ManualReset

I encourage you to download the project below and run it to see exactly how it works. This sample covers the Manual method of context switching between threads.

This example was also included in the previous download link, but here it is again for your convenience. The best way to understand both the threading examples is to download and run them. The two examples will show you the difference between a manual reset and auto reset.

Click here for the download

image

Enabling Source Code Support

Before debugging threads, we need to tell Visual Studio to enable source code debugging. This is easy to do. Just go to the Tools menu and select Options. Navigate to the debugging area and check the following boxes as seen below:

image

To get things started, we will say debug, start debugging. Next we will hit the button whose caption reads create threads. At this point 5 threads have been created, each one is blocked or in a wait state. Now we wish to break in to the debugging session by going to the debug menu and selecting break all.

To start things off where we’ll start debugging the application as you can see in step one. Next we will click the create threads button in step two. In step three select the break all menu selection from the debug menu.

image

At this point, we would like to see which threads are operating inside of our application. That is what the threads window is used for. So display the threads window.

image

All 5 threads can be seen above. All of them are started but are in a “wait” state. They are blocked at this time.

The goal

With five threads running setting a breakpoint becomes challenging. It may become necessary to want only one of the threads to hit the breakpoint. Notice in the debugger threads window, each thread has an ID. It is with this ID that we can tell the debugger to break for a specific thread.

Step 1: Set a breakpoint as seen below

Step 2: Run the application. The debugger output window will display the thread numbers.

Step 3: Right click on breakpoint and set a “Filter” on one of the threadnumbers.

Step 4: Go back to the main form of the application and choose “Close Threads”.

We will start by setting a generic breakpoint inside of our code. Once that is done we can apply the breakpoint filter.

At this point we can just choose any one of our worker threads. We will choose thread number 1828 just for their heck of it.

image

By right mouse clicking on the breakpoint we can choose the filter menu item. Notice that this dialog box allows us to specify a machine, the process, and a thread, by either ID or by name, we will simply type in thread ID equals 1828.

image

At this point we’re ready to continue. So go to the debug menu and select continue. At this stage the applications threads are in a wait state (meaning they are blocked on the waitHandle.WaitOne() code statement). But if we go to the applications in main form and choose close threads, they will become unblocked and therefore the threads will be allowed to continue to execute. However, only thread 1828 will hit the breakpoint because that is what we specified in the breakpoint filter modifier.

 

image

But that is the big take away and this lesson.  Being able to hit a breakpoint for a specific thread is critical when debugging.