We can identify threads created using both the BackgroundWorker component and the Thread class. We can also identify 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. 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 be learning shortly.
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.
Time for action – 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. Thus, let's freeze the threads we are not interested in!
Now, we are going to leave one encryption thread running alone to focus on its code without the other threads disturbing our debugging procedure:
- Stay in the project, SMSEncryption.
- Clear all the breakpoints. Press Ctrl + Shift + F9 or select Debug | Delete AllBreakpoints in the main menu. Make sure the Threads window is visible.
- Define a breakpoint in the line int liThreadNumber = (int)poThreadParameter; in the ThreadEncryptProcedure procedure code.
- 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:
- 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.
- 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.
- 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:
- Run the code step-by-step inspecting values as you do with single-threaded applications.
What just happened?
It is easy to debug a multi hreaded 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 multi threaded 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.
Freezing and thawing threads
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 ti me 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.
Viewing the call stack for each running thread
Each thread has its own independent stack. Using the Call Stack window, we can move through the methods that were called, as we are used to doing so in single-threaded applications.
The main difference in doing this with multithreaded applications is that when the active thread changes, the Call Stack window will also show different content.
Debugging a multi hreaded application using the techniques we are learning is an excellent way to understand how the different threads run and will improve our parallel programming skills.
To show the call stack for the active thread, press Ctrl + Alt + C or go to Debug | Windows | Call Stack in the main menu. Make sure the Threads window is also visible to take into account the active thread when analyzing the call stack, as shown in the following image:
Have a go hero – Debugging and enhancing the encryption algorithm
Using the multithreaded debugging techniques we have learned so far, develop a new version of this application with the encryption problem solved. Take into account everything we have studied about freezing and thawing threads.
Check the randomly generated garbage and the way it is applied to the generated encrypted string. Making some changes to it, you can have a robust encryption process that differentiates each output with the same input text.
You can improve the new versions by using new randomly generated garbage to enhance the encryption algorithms.
Oh no! You have to explain to the agents the changes you made to the encryption procedure, and how it works.
Showing partial results in multithreaded code
We have learned new debugging features provided by Visual C# that help us a lot in troubleshooting bugs in multithreaded applications without dying in action–transforming it into a single-threaded application without making changes in its code. However, sometimes, we must show partial results of a procedure to help us understand what is happening while it is running. How can we safely show partial results in procedures being executed in many concurrent threads?
We cannot show the partial results in the UI controls. We can do that using many BackgroundWorker components, but it would require lots of changes to the code and add a great complexity to the application. We do want to take a simpler approach.
Fortunately, the IDE offers us the Immediate Window, and it can help us in our task.
Time for action – Explaining the encryption procedure
The FBI agents want you to explain to them the encryption procedure, showing them partial results. Using the debugging techniques learned so far, you isolate an encryption thread and begin executing it step-by-step, inspecting variables to show the values. Nevertheless, they do not understand it this way. They want you to copy and paste the partial results in a document and then explain to them using what just happened. Don't worry; using the Immediate Window, it is very easy!
Now, we are going to add code to the Encrypt procedure to show partial results in the Immediate Window as the text is being encoded:
- Stay in the project, SMSEncryption.
- Clear all the breakpoints. Press Ctrl + Shift + F9 or select Debug | Delete All | Breakpoints in the main menu. Make sure the Threads and the Immediate Window are visible.
- Select Tools | Options in the main menu. The Options dialog box will appear. Make sure the option Redirect all Output Window text to the Immediate Window is checked in the Debugging | General page. This way, you will see the messages in the Immediate Window.
- Add the following line of code at the beginning of the Encrypt procedure:
// Show the original text in the Immediate Window
System.Diagnostics.Debug.Print("Original text:" + psText);
- Add the following line of code after the line loRandomChar = (char)(loRandom.Next(65535)); in the Encrypt procedure:
// Show the random char code (in numbers) generated in the
System.Diagnostics.Debug.Print("Random char generated:" + ((int)
- Add the following line of code after the line lsEncryptedText +=loRandomChar.ToString(); in the Encrypt procedure:
// Show how the encrypted text is being generated in the
System.Diagnostics.Debug.Print("Partial encryption result char
number: " + i.ToString() + ": " + lsEncryptedText);
- Add the following line of code before the line return lsEncryptedTextWithFinalXOR; in the Encrypt procedure:
// Show how the encrypted text is being generated in the
System.Diagnostics.Debug.Print("Final encryption result with XOR:
" + lsEncryptedTextWithFinalXOR);
- 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 and click on the Run in a thread button. You will see that partial results show in the Immediate window as shown in the following image:
What just happened?
The results shown in the Immediate Window are a great mess. How can you explain that to the FBI agents? The answer would be to isolate an encryption thread and show the results for just that one thread using the debugging techniques learned. Then, you can copy the contents of the Immediate Window to a word processor!
The outputs shown in the Immediate Window are the results of the code executed in many concurrent threads. Therefore, it is very difficult to understand the real encryption execution sequence.
One possible solution is isolating one thread and running just that one. We have already learned how to do so.
The other is to show the information only when it is running on a certain thread. We can check it in the procedure executed by the thread using Thread.CurrentThread and comparing the name.
As we can see, Thread.CurrentThread is very useful in defining conditional execution of certain code according to some information provided by the thread context in which the methods are being run. Besides, it is very useful while debugging.
Showing thread-safe output
The System.Diagnostics.Debug.Print method allows us to show information at runtime from many threads in a thread-safe way. Using it, the application can provide us with very important feedback to understand what happens when multiple concurrent threads are running at the same time.
Running a multithreaded application step-by-step, even without isolation, does not represent the real concurrent execution that happens at runtime without breakpoints. Therefore, it is very important to have some feedback to test certain conditions using the System.Diagnostics.Debug.Print method.
However, we must remember that the application's performance will degrade when using the System.Diagnostics.Debug.Print method. Thus, the performance must be measured without calls to this method or any interfering debugging techniques.
Using this method, combined with all the debugging techniques known for single-threaded applications, such as inspecting values, setting breakpoints, and tracepoints, we will not have any trouble in solving bugs in multithreaded code.
Time for action – Isolating results
Using the debugging techniques learned to isolate a single thread, you can show the results of one encryption thread in the Immediate Window. This way, you will be able to explain to the FBI agents what just happened in the encryption procedure. Furthermore, finally, they will be happy with you!
Now, we are going to isolate the thread as we did in a previous example, but using our recently added code to the Encrypt procedure to show partial results in the Immediate Window as the text is being encoded:
- Stay in the project, SMSEncryption.
- Define a breakpoint at the line int liThreadNumber = (int)poThreadParameter; in the ThreadEncryptProcedure procedure code.
- Enter or copy and paste a short text, using the same lines (with as many lines as cores available, for example, four in a computer with a quad-core microprocessor) in the Textbox labeled Original SMS Messages.
- 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 is shown with a yellow arrow on the left in the Threads window.
- Freeze the threads you do not need as we learned to do in the previous examples and run the application to obtain the progress of the encryption procedure shown in the Immediate Window, as shown in the following image:
What just happened
You explain the encryption procedure to the FBI agents, step-by-step with the example you obtained in the Immediate Window. They are amazed with the simplicity of your examples and your parallel processing skills! They think you are ready to cooperate with a very interesting project in NASA. But, before that, there is a new mission for you.
As we isolated one encryption thread, the text shown in the Immediate Window is the result of the linear execution of just one thread.
Isolation is a great resource for debugging multithreaded applications. Using it, we can easily find and solve bugs.
Understanding thread information in tracepoints
When debugging multithreaded applications, the information shown by the tracepoints is usually useful. They can be used in a manner similar to their usage when debugging singlethreaded applications. But they show us information about the running thread.
In order to solve bugs, sometimes we need to know whether a method was executed or not. In a multithreaded applicati on, this can be more confusing, especially when we are writing our first parallelized structures.
To create a tracepoint, you must right-click the line of code and select Breakpoints | Insert Tracepoint in the context menu that appears. The When Breakpoint Is Hit dialog box will be shown, asking for the parameters for the behavior of the tracepoint, as shown in the following image:
When debugging multithreaded applicati ons, the $TID and $TNAME keywords are very important because they will give us information in the Immediate Window with the thread ID and its name, as shown in the following line:
Function: SMSEncryption.frmSMSEncryptionEngine.ThreadEncryptProcedure(object), Thread: 0x7FC Encryption #0
When you need to know the methods the concurrent threads are running, using tracepoints is a very good choice, because you do not need to change the code to show results in the Immediate Window.
Have a go hero – Concurrent decryption
As mentioned earlier, there is still some work left to be done. The FBI wants to test the time needed to break the encryption. Remember, they have a computer with 16 quad-core microprocessors (64 cores). They do not want you to break the code, but they ask you to create a new application that decrypts each encrypted code. That is, you must decrypt the code simultaneously as the other threads are encrypting the original code. You have to use a queue scheme. The encryption threads add encrypted code to that queue, while the decryption threads remove code from it.
Using everything that we know and the debugging techniquesstudied in this article, develop a new version of an application that shows the incoming and outgoing SMS messages with their information in a grid. Use half of the processing power to encrypt and the other half to decrypt concurrently. The text to be decrypted must be entered or copied by the user. Therefore, the user interface must show the progress but must be responsive to the user.
While many threads are encrypting text, the user can copy the results and paste them in the decrypti on input TextBox.
The encryption and decryption processes must be launched by different buttons.
Enhance the new application detecting the activity of the concurrent threads. If there is no decryption process, the encryption engine must work with as many threads as cores available, and vice versa. It must work in a dynamic way to achieve the best possible performance at any time. Do not be disappointed! You can do it!
In this article, we saw how to freeze many threads to allow us to debug one thread at a time, without the problems related to concurrency while executing the application step-by-step. We inserted breakpoints and tracepoints to simplify troubleshooting complex multithreaded applications.
We also generated partial results information without creating problems for the multithreaded code.