Debugging C Sharp Threaded Programming
From TroubleshootingWiki
The correct title of this article is Debugging C# Threaded Programming. The substitution or omission of a # sign is because of technical restrictions for naming articles.
| Official Page |
| Project Documentation |
| Download |
|
In order to solve problems that arise in concurrently running threads in our applications, we need new debugging techniques suitable for the new parallelism environments that occur in Visual C#.
Contents |
[edit] Watching multiple threads
The BackgroundWorker component and then the Thread class are used to create new threads independent of the main application thread. The applications can respond to UI events, while the processing continues, and take full advantage of multiple cores, and can thus run faster. However, we are used to debugging applications that run in just one thread (the main thread), and there are many changes in the debugging process that generate great confusion when following the classic procedures running many concurrent threads. How can we successfully debug applications that are running many concurrent threads?
We can use some techniques when we have to control and coordinate the debugging process of many concurrent threads. When an application is multithreaded, we are not alone. There is code running everywhere at any given time in the same application process context. Therefore, when we are debugging an application step-by-step, in each step, the other concurrent threads can make several changes to many things we are inspecting or debugging. It sounds very confusing (and indeed, it is). Fortunately, the IDE offers us many tools that can help us a lot in the debugging process of a multithreaded application.
Understanding the difficulty in debugging concurrent threads
Your cellular phone rings! The FBI agents have detected a problem with the encryption engine. When the application receives the same messages many times during a certain period, the encryption process generates exactly the same results, as shown in the following image:
Thus, hackers could easily break the code once they discover this important bug. They ask for your help again, but this time, very gently. Of course, you want to cooperate because you do not want the FBI agents to get angry with you again. However, you need to debug the multithreaded encryption engine, and you have never done that! Let's create a solution for this problem!
First, we are going to try to debug the multithreaded application the same way we do with a single-threaded application to understand the new problems we might face:
1. Open the project, SMSEncryption.
2. Define a breakpoint in the line int liThreadNumber = (int)poThreadParameter; in the ThreadEncryptProcedure procedure code.
3. Press F5 or select Debug | Start Debugging in the main menu.
4. Enter or copy and paste a long text (with more than 5,000 lines) in the Textbox labeled Original SMS Messages and click on the Run in a thread button.
The line with the breakpoint defined is shown highlighted as the next statement that will be executed.
5. Press F10 or select, Debug | Step Over in the main menu two or three times (depending on the number of cores you have in the computer).
As you can see, the next statement that gets executed is the same even when you try to go on with the next one.
It seems that the statement is not being executed.
However, inspecting the value of poThreadParameter (the parameter passed to the ThreadEncryptProcedure procedure) shows that
it changes each time you step over the statement, as shown in the following image:
6. Stop the application and repeat the steps 1 to 5 to make sure you are not crazy because of parallelism, multithreading, and the FBI agents!
What just happened?
The debugger executed each new Thread class instance call to the Start method, with this line:
prloThreadList[liThreadNumber].Start(liThreadNumber)';
Then, it entered in the ThreadEncryptProcedure method (remember we used the same method for every created encryption thread) with different values for thepoThreadParameterparameter. Therefore, you stayed in the same statement as many times as the threads were created (equivalent to the number of cores available in the computer) in the following line:int liThreadNumber = (int)poThreadParameter;
|
As we can see, debugging this way is very confusing, because the IDE switches from one thread to another, and you loose control over the statements that are going to be executed next. In a debugging process, you need to know in which part of the application you are. |
As we tested our first attempt to debug a multithreaded application, we tried the same technique as with single-threaded applications.
There are new subjects to learn and new techniques to use.
[edit] Debugging concurrent threads
When we need to inspect values, execute a procedure step-by-step, and find solutions to problems related to some specific code, the best way to achieve that with a multithreaded application is to work with it as a single-threaded application. But, how can we do that? It is very simple. We must run one thread at a time and freeze the other concurrent threads while we are debugging the thread in which we are interested and on which we are focusing.
When we debug single-threaded applications, we are aware of the method in which we are positioned and its context. In multithreaded applications, we must also be aware of the thread in which we are positioned. If we do not know in which thread we are executing statements, we will be completely confused in just a few seconds, as happened in our previous activity.
|
We must tailor our multithreaded applications to simplify the debugging process. If we do not do this, the debugging process will be a nightmare. Indeed, we do not want that to happen! |
Finding the threads
Now, we are going to use the IDE features to help us find the threads in a multithreaded application:
1. Using the same project that we used in the previous example, with the same breakpoint defined, press F5 or select Debug | Start Debugging in the main menu.
2. Enter or copy and paste a long text (with more than 5,000 lines) in the Textbox labeled Original SMS Messages and click on the Run in a thread button. The line with the breakpoint defined is shown highlighted as the next statement that will be executed.
3. Select Debug | Windows | Threads in the main menu or press Ctrl + Alt + H. The Threads window will be shown, displaying all the threads created by the application process, as shown in the following image:
4. The yellow arrow in the left of the thread list points out the current thread the thread for which the IDE is showing the current statement.
5. Press F10 or select Debug | Step Over in the main menu. As you can see, the next statement is the same again, but the current thread pointed out in the thread list changes, as shown in the following image:
6. Go on running the application step-by-step and watch how the current thread changes. Observe the Threads window throughout your debugging process.
What just happened?
You found the threads in the debugging process. Now, you believe you will be able to make the necessary changes to the application if you learn a few debugging techniques quickly.
The Threads window displays the list of threads created by the application process. Many of them are created automatically by the C# runtime. The others are created by the Thread class instances and the BackgroundWorker component we have in the application.
Using the Threads window, we can easily determine in which thread we are executing when debugging a multithreaded application. It is indeed very helpful.
|
Remember that each thread has its own stack. |
[edit] Understanding the information shown in the Threads window
The Threads window displays the following information for each thread:
- ID : A unique numeric thread identifier.
- Name: The thread's name (the
Nameproperty). If the thread'sNameproperty was not assigned, it has anullvalue,and the window will show <No name> in this column, as shown in the following image:
- Location: The method the thread is running. In the previous image, the thread with its Location presented as
SMSEncryption.Program.Mainis the main thread and the threads with their Location set asSMSEncryption.frmSMSEncryptionEngine.
ThreadEncryptProcedure are the ones created as instances of the Thread class by our dynamic thread-manufacturing algorithm.
- Priority: The priority assigned to the thread. The possible values are similar to the ones learned and experienced for the processes.
|
No matter what the priority settings are, the results always depend on the scheduler decisions based on the number of running threads and their resources usage. |
- Suspend: A
boolvalue indicating whether the thread is running or paused. A 0 means the thread is running and a 1 means that it is suspended. When a thread is suspended, a pause icon is shown on the left, as shown in the following image:
|
The problem with our applications is that we did not use names for the threads, and they are using the same method ( |
Assigning names to threads
You must identify each thread in the Threads window. This way, you will be able to easily find the problems in the encryption engine without getting confused.
Now, we are going to make some changes in the Thread class instances creating code, and to the BackgroundWorker in order to simplify the thread identification process during debugging:
1. Stay in the project, SMSEncryption.
2. Open the Click event in the button butRutRunInThread, and add the following code at the beginning:
// Give the main thread a nameThread.CurrentThread.Name = "Main thread";
3. Add the following line of code before starting each thread with the line prloThreadList[liThreadNumber].Start(liThreadNumber); in the for (liThreadNumber = 0; liThreadNumber priProcessorCount; liThreadNumber++) loop, in the Click event handler in the button butRunInThread:
// Give the thread a name
prloThreadList[liThreadNumber].Name = "Encryption #"+ liThreadNumber.ToString();
4. Open the <code>DoWork event in the BackgroundWorker bakShowEncryptedStrings and add the following code at the beginning:
// Give the BackgroundWorker thread a name Thread.CurrentThread.Name = "bakShowEncryptedStrings";
5. Keep the breakpoint we defined in the previous example.
6. Define a new breakpoint in the line priLastEncryptedStringShown = 0;in the BackgroundWorker bakShowEncryptedStrings DoWork event.
7. Press F5 or select Debug | Start Debugging in the main menu. Make sure the Threads window is visible.
8. Enter or copy and paste a long text (with more than 30,000 lines) in the Textbox labeled Original SMS Messages and click on the Run in a thread button. The line with the breakpoint defined in the ThreadEncryptProcedure procedure is shown highlighted as the next statement that will be executed. But, the threads are easily identified by their names in the Threads window, as shown in the following image:
9. Press F5 or select Debug | Start Debugging in the main menu many times until you reach the breakpoint defined in the BackgroundWorker code. You will see a new thread with the bakShowEncryptedStrings name defined, as shown in the following image:
10. Go on running the application step-by-step and watch how the current thread changes. Observe the Threads window throughout your debugging process.
What just happened?
Now, you can easily identify each thread in the Threads window. The only thing you need is to isolate one thread, to understand what is going on in the encryption process.
Before starting with the threads, we assigned them a name. We did that for the main thread, the BackgroundWorker thread and the encryption threads. Thus, the Threads windows showed their names, and we can take control of the concurrent threads.
The following line assigns a string value to the Name property of the Thread class instance, taking into account the thread number (remember that we are creating them dynamically according to the number of available cores):
prloThreadList[liThreadNumber].Name = "Encryption #" + liThreadNumber.ToString();
If we have four cores available, the resulting names will be the following:
- Thread #0: "Encryption #0".
- Thread #1: "Encryption #1".
- Thread #2: "Encryption #2".
- Thread #3: "Encryption #3".
|
It is a good practice to give each thread a name. It is very important to simplify the debugging process and to share code with other developers. |
[edit] Identifying the current thread at runtime
A Thread class instance provides access to the Name property. As mentioned earlier, it allows us to set the name, and it is shown in the Threads windows. Nevertheless, the BackgroundWorker does not provide a direct method or property to set the name of the thread it creates.
For that reason, we use the CurrentThread property of the Thread (System.Threading.Thread) class. It offers access to a Thread instance of the currently running thread, that is, the thread running the current method. As it is an instance of the Thread class, it has a Name property.
The following line accesses the current thread of the BackgroundWorker and assigns the name for that thread, through Thread.CurrentThread:
Thread.CurrentThread.Name = "bakShowEncryptedStrings";
The current thread is the one created by the BackgroundWorker as we are in the DoWork event handler code.
This is shown in the following image:
|
Be careful with the BackgroundWorker because the |
The following line accesses the current thread (the main thread) and assigns the name for that thread, again using Thread.CurrentThread:Thread.CurrentThread.Name = "Main thread";
Of course, we can also use Thread.CurrentThread in theThreadEncryptProcedure, to assign the names for each created thread as the first instruction, instead of doing it before calling the Start method, as shown in the following line:
Thread.CurrentThread.Name = "Encryption #" + ((int)poThreadParameter).ToString();
Thread.CurrentThread can also be used to access the current thread properties and methods.
For example, we can access the IsBackground property with the following expression:
Thread.CurrentThread.IsBackground
Then, we can evaluate it for any purpose.
|
We can also evaluate the |
[edit] Debugging multithreaded applications as single-threaded applications
So far, we have identified threads created using both the BackgroundWorker component and the Thread class. We also identified the main application thread and we learned about the information shown by the Threads window. However, we must debug the encryption process to solve its problem without taking into account the other concurrent threads. How can we successfully debug the encryption engine focusing on one thread and leaving the others untouched?
We can use the Threads window to control the execution of the concurrent thread at runtime without having to make changes to the code. As we learned in the previous tutorials, this will affect the performance results, but it will allow us to focus on a specific part of the code as if we were working in a single-threaded application.
|
This technique is suitable for solving problems related to a specific part of the code that runs in a thread. However, when there are problems generated by concurrency we must use other debugging tricks that we will learn later in this tutorial. |
The Threads window does a great job in offering good runtime information about the running threads while offering a simple way to watch, pause, and resume multiple threads.
Leaving a thread running alone
You must run the encryption procedure called by ThreadEncryptProcedure. But you want to focus on just one thread, in order to solve the problem that the FBI agents detected. Changing the code is not an option, because it will take more time than expected, and you might introduce new bugs to the encryption engine. Now, we are going to leave one encryption thread running alone to focus on its code without the other threads disturbing our debugging procedure:
1. Stay in the project, SMSEncryption.
2. Clear all the breakpoints. Press Ctrl + Shift + F9 or select Debug | Delete All Breakpoints in the main menu. Make sure the Threads window is visible.
3. Define a breakpoint in the line int liThreadNumber = (int)poThreadParameter; in theThreadEncryptProcedure procedure code.
4. Enter or copy and paste a long text, using the same lines (with more than 30,000 lines) in the Textbox labeled Original SMS Messages, as shown in the following image:
5. Click on the Run in a thread button. The line with the breakpoint defined in the ThreadEncryptProcedure procedure is shown highlighted as the next statement that will be executed. The current thread will be shown with a yellow arrow on the left in the Threads window.
6. Right-click on each of the other encryption threads and select Freeze in the context menu that appears, in order to suspend them. If the current thread is Encryption #1 and there are four cores available, you will freeze the following threads Encryption #0, Encryption #2, and Encryption #3.
7. Right-click on the Main thread and select Freeze in the context menu that appears, in order to suspend it (we do not want the BackgroundWorker to start and interfere with our work). The only working thread that matters will be Encryption #1, as shown in the following image:
8. Run the code step-by-step inspecting values as you do with single-threaded applications.
What just happened?
It is easy to debug a multithreaded application focusing on one thread instead of trying to do it with all the threads running at the same time.
We could transform a complex multithreaded application into a single-threaded application without making changes to the code.
We did it at runtime using the multithreading debugging features offered by the C# IDE.
We suspended the execution of the concurrent threads that would disturb our step-by-step execution. Thus, we could focus on the code being executed by just one encryption thread.
[edit] Freezing and thawing threads
As we learned in the previous example, freezing a thread suspends its execution. However, in the debugging process, we would need to resume the thread execution.
It can be done at any point of time by right-clicking on a suspended thread and selecting Thaw in the context menu that appears, as shown in the following image:
|
By Freezing and thawing threads (suspending and resuming), we can have an exhaustive control over the threads running during the debugging process. It helps a lot when we have to solve bugs related to concurrency as we can easily analyze many contexts without making changes to the code which could generate new bugs. |
Nevertheless, when developing multithreaded applications, we must always test the execution with many concurrent threads running to make sure it does not have concurrency bugs.
The debugging techniques allow us to isolate the code for evaluation purposes, but the final tests must use the full multithreading potential.
[edit] Source
The source of this content is Chapter 5: Debugging C# Threaded Programming of C# 2008 and 2005 Threaded Programming: Beginner's Guide by Gastón C. Hillar (Packt Publishing, 2009).

