Introducing Multithreading


Through the years while developing on the mainframe, we all have grown com fortable with terms such as "multiprogramming," "multiprocessing," and "multitasking . " With mainframe COBOL batch development, the distinction between multiprogramming, multiprocessing, and multitasking basically pointed out a level of granularity with concurrent processing. Let's review.

At a high level, multiple mainframe system initiators would concurrently process JCL Jobs. Each JCL Job had individual Job Steps that supported the con currently processing Jobs. Each Job Step would execute your COBOL programs. Then, at a much lower level, the mainframe operating system would create one task from which resource management took place. As you know, the executing program had the option to create additional tasks (or subtasks ). Coming from the mainframe, you may be more familiar with the acronym TCB, which stands for Task Control Block. All of these levels of "multi" processing are commonly known on the mainframe.

Now, it is time to learn about another "multi" term : multithreading. Generally speaking, when you use multithreading in your .NET Windows and Web appli cations, you are enabling your application to leverage multiple threads of execution ”virtually at the same time. In some cases, you take advantage of multithreading without even being aware of it. For example, the most recent .NET sample application (in the section "Using MSMQ for Asynchronous Processing") demonstrated multithreading.

Think back for a moment. Recall the BeginReceive method that began the asynchronous process for you. At that time, I mentioned that the BeginReceive asynchronous process would be "running in the background." The fact is that the BeginReceive method created a new thread of execution. Because the new thread of execution (or simply, the thread ) was created as a low-priority worker thread, the primary/main thread (running in the foreground) was not "blocked" by the lower priority thread that "waited" in the background.

For another moment, think further back ”all the way back to Chapter 8. Recall the discussion about the garbage collector (GC). There, I mentioned that the CLR's GC ran in the background. Now, you can easily appreciate what the ref erence to "running in the background" really means. The GC is there, running on a lower-priority background thread.

Note  

Multithreading is a .NET feature that you should only use under three conditions. First, you absolutely must understand how to correctly implement the technique into your application design. Second, you must take your application through a rigorous testing phase. Third, you must first consider whether or not your application actually needs the benefits offered by multithreading.

Let's explore multithreading further.

Putting Multithreading into Perspective

You might wonder if the mainframe task is analogous to the Windows thread. The answer is yes and no. Yes, a thread is used by the operating system for scheduling and execution. No, a thread does not own memory and other resources. When it comes to the "owner" of resources, then you are really talking about the Windows process . As you will soon see, this is just one piece of the puzzle. Let's chat for a moment about processes.

Caution  

Be forewarned. The discussion that follows may appear to be rather abstract. However, if you want to efficiently , safely, and correctly use multithreading, you should give consideration to understanding each detail (and then some) contained in this section.

Defining a Process

You can easily view your active processes by launching the Windows Task Manager. Take a moment to press Ctrl-Alt-Delete to view your active processes. As shown in Figure 20-30, the Processes tab displays any active processes.

click to expand
Figure 20-30: Viewing the Windows Task Manager's Processes tab. For demonstration purposes, I have executed the recent MSMQ sample applications.

In Figure 20-30, you will notice the recent MSMQ sample applications shown as processes. Additionally, you will notice that I have singled out three other pro cesses: Aspnet_wp.exe, Inetinfo.exe, and Dllhost.exe. A complete understanding of processes will bring you to the point of knowing the purpose behind Aspnet_wp.exe, Inetinfo.exe, and Dllhost.exe.

Say that you just created an ASP.NET Web application. Then, being curious , you explored your Computer Management console MMC snap-in and noticed that you could configure your ASP.NET Web application (in the Computer Man agement console, select Services and Applications Internet Information Services, and then right-click your Web application and select Properties Directory Tab Application Protection). You would find that choosing the Appli cation Protection choice of Low (IIS Process), Medium (Pooled), or High (Isolated) would cause your Web application to "run" within either the Inetinfo.exe (Low) or Aspnet_wp.exe (Medium or High) process. Hmmm, interesting.

Note  

Using the Application Protection choice of Medium will result in sharing one instance of the Aspnet_wp.exe process. The Application Protection choice of High will result in a dedicated instance of an Aspnet_wp.exe process. Pre-.NET Web applications (ASP) used the Dllhost.exe process when either Medium or High was chosen for Application Protection.

Now, let's say that you decided to create another class library (.dll) that would be hosted in Enterprises Services (COM+). Recall that this is called a serviced com ponent . Regardless of which approach you decided to take (dynamic registration, Regasm.exe, or Regsvcs.exe) to install your serviced component into the COM+ catalog, you would need to choose an activation type.

If you choose to have your COM+ application run with the activation type of Library, you'll run it in-process. If you choose to have your COM+ application run with the activation type of Server, you'll it run out-of-process. What process do you think this all applies to? That's right! Allow me to introduce to you the Dllhost.exe process. When your serviced component is running in-process, it's running inside of the client's process. Otherwise , the out-of-process choice has your serviced component running inside the Dllhost.exe process.

Can you see the implications here? If you have a simple .NET Windows appli cation that is not using a serviced component, it will run in its own process ”its own address space. On the other hand, if you have an ASP.NET Web application that happens to consume an in-process serviced component, the processes that you are actually concerned about could be either Aspnet_wp.exe or Inetinfo.exe, and possibly Dllhost.exe. Hmmm, interesting.

Now that you have a good understanding of processes, let's drop down a notch in terms of granularity. Before I discuss the infamous thread, I need to introduce two more pieces of the puzzle: the application domain and the context.

Application Domains and Contexts

In short, you can find one or more application domains in a .NET application's process. An application domain will host one or more contexts. Objects live in a context. Now, that wasn't that bad, right? Good. Let's continue.

Unless you specify otherwise, your code executes within the boundaries of the default application domain. A default context is created for you as well. Depending on the needs of an object and the characteristic of each particular context, an object is likely to "jump" from one context to another unless the object is a context-bound object.

Tip  

Consider exploring the .NET Framework's System.AppDomain class. You will want to learn more about default domains and the explicit creation of application domains using the System.AppDomain.CreateDomain method. Additionally, give strong consideration to referring back to the section "Policy Hierarchy" in Chapter 18. There you will find your first introduction to the AppDomain class. Yes, this is the same AppDomain class that you learned about in the security policy hierarchy discussion.

Application domains are made apparent each time you end a debug session while in the VS .NET IDE. You will notice that the VS .NET IDE Debug window reports on which assemblies were loaded into your application domain. You will recognize a context-bound object has either of the following characteristics: it is an object created by a class that inherits from the base class of System.ContextBoundObject class, or it is an object that uses the SynchronizationAttribute attribute from either the System.Runtime.Remoting.Contexts namespace or the System.EnterpriseServices namespace.

Note  

You might have noticed some overlap between the topics of multithreading and Remoting. That is rather perceptive of you. An understanding of application domains (and even context-bound objects) will serve you well as you further explore either multithreading or Remoting.

Now that you understand the basic idea behind processes and application domains, let's talk about the thread.

The Thread of Execution

The thread executes your managed code (your managed procedures). This thread of execution can take place within the boundaries of the default application domain. Multiple threads of execution can take place within one application domain. Additionally, one thread of execution can cross appli cation domains. Apart from how a thread relates to an application domain, it may help to also know that a thread stores the actual stack, the current state of the CPU registers, and execution schedule information.

When you worked with MSMQ earlier in this chapter, the BeginReceive asyn chronous method created a multithreading scenario for you. You should look at this as an implicit use of multithreading. Naturally, you will want to know how to explicitly work with threads to manipulate your custom multithreading pro cessing models.

So, are you ready for multithreading? You think so, huh? Well, let me share a few more thoughts with you. Afterward, I promise, you will look at some multi- threading code.

A Bug to Remember

Many mainframe moons ago, there was an occasion when I was rewriting a COBOL program. The rewrite was simply to move from the ANSI 74 standard to the ISO/ANSI 85 standard. After I completed the rewrite, an intermittent bug was dis covered. I recall spending several days trying to track down this problem. I was eventually able to narrow the problem down to two statements that followed one after the other. One statement was an OPEN statement. The other statement was a simple READ. The strangest thing was that this portion of the logic had been "ported" over as-is from the original program, a simple OPEN statement followed by a READ statement. To make matters worse , other OPEN and READ statements that were not paired together as these two were, were working just fine.

Ultimately, I resorted to recompiling both the original ANSI 74 standard COBOL and the rewritten ISO/ANSI 85 standard program. I chose the compile option (LIST) that produced an assembly language translation listing. After doing a side-by-side comparison (from one program to the other), targeting just the two suspect lines, the cause of the problem became apparent. There, buried between nearly a dozen lines of assembly language code, I found a difference between the two programs. On the surface, both programs used the same two high-level state ments. Underneath, at a lower level, the code was composed of nearly a dozen significantly different assembler language statements.

Apparently, the compiler, in its effort to produce optimized code, translated these two OPEN and READ statements in a different way than the other OPEN and READ statements. My fix was to separate these two high-level statements in an insignificant way. This was enough to trick the compiler into creating the expected correct assembler code.

You certainly recall the previous discussions about Microsoft intermediate language (MSIL) and the just-in-time (JIT) optimizations that occur with your .NET managed code. Do you see where this is leading? That's right! Watch out. Be aware of the code that actually runs for you. Ultimately, that is the code that will matter. In the end, it's not what the high-level code looks like that matters. Rather, it's what the high-level code gets translated/optimized to that matters.

Generally, you make design decisions based on the high-level code that you "see." How the low-level version of your application runs will impact any design decisions that you have made ”decisions made based on the high-level code view ”for better or for worse. This becomes all the more critical when you are designing a multithreaded application.

Did you ever imagine that multithreading carried these types of low-level considerations?

Low-Level Architecture: Why?

My first exploration into multithreading left me questioning the need to really understand low-level architecture topics such as context-bound objects and so forth. It was not until I learned about the multithreading concerns of potential deadlocks, thread pool management, thread time slicing and starvation , unsyn chronized collisions, the dangers of global variables, and corrupted shared variables that I decided to dig down further.

Yes, you can easily use multithreading without knowing or caring about context-bound objects and other low-level technical information. However, when those pesky intermittent bugs start showing up, you may regret that you ever heard the word "multithreading." After you have spent hours, days, or weeks ( assuming that you have not gotten fired by that point) trying to figure out why the simple multithreading logic seems to work most of the time but not all of the time, you will want to understand what is really going on. You will appreciate what a lower-level understanding of your .NET environment really means.

Therefore, starting with a firm understanding about processes will introduce you to application domains. An understanding about context-bound objects and application domains implies that you are ready to be introduced to synchroni zation domains and the SynchronizationAttribute attribute. Understanding the value of synchronization domains means that you might have already learned about protecting the integrity of your shared variables with the SyncLock statement. Truly understanding when to use the SyncLock statement certainly means that you will have become equally comfortable with several System.Threading classes (i.e., InterLocked , Monitor , and Mutex ).

Tip  

Some write-ups on the topic of multithreading seem to only mention the use of the SyncLock statement when you want to achieve synchronization. A more complete exploration will unearth at least four additional techniques: synchronization domains and the three System.Threading classes InterLocked, Monitor, and Mutex. Each synchronization technique is designed for specific scenarios. Each technique, including the SyncLock statement, has strengths and weaknesses. You should be aware of each choice and choose wisely.

With all this talk about synchronization, perhaps you may have gathered that all you really have to do is use any one of the available synchronization techniques. This is partially correct. Let's take a quick look at what I like to refer to as the ".NET Framework thread-safe" factor. As you will see, there is much to consider.

Is the .NET Framework Thread-Safe?

As you know, the .NET Framework is composed of many high-level classes and members . Fortunately, Microsoft labels each class as either being thread-safe or not thread-safe. Unfortunately, many of the .NET Framework classes are not thread-safe. This simply means that using any of the synchronization techniques will be that much more important when you design a multithreaded application that uses a not-thread-safe class.

Although Microsoft's NET Framework documentation states that all .NET Framework public static members ( methods , properties, and fields) are thread-safe, there is still cause for concern. Notice I used the term "concern," not "alarm." When a class is not thread-safe, this simply means that you should be aware of this fact. Additionally, you should take this fact into consideration when you design your applications.

start sidebar
Do You Remember the Application State Management Topic?

In Chapter 15, I suggested that you "use the Lock and Unlock methods to protect against concurrent update collisions." Now it should be easier for you to appreciate the whole purpose behind that suggestion. Recall that the concern surrounded the possibility of updating the application state at any point outside the Start and End application-level events/methods. In that case, there was a possibility that two threads may have tried to update the same application state information at the same time.

The System.Web.HttpApplicationState.Lock method blocks other threads from accessing the application state variables. The companion method, System.Web.HttpApplicationState.UnLock , removes the block. In this case, the Lock and Unlock methods are implementing a form of synchronization to protect the integrity of the application variables that is made necessary by the concurrent processing of sessions on separate threads.

end sidebar
 

To give you a sense of the thread safety notifications to look for when you research the .NET Framework Help text, I have prepared a series of figures. I captured each figure in the series directly from the Microsoft Visual Studio .NET documentation tool. Let's start with Figure 20-31, which reflects an example of a completely thread-safe .NET Framework class.

click to expand
Figure 20-31: The thread safety notice for the String class

As shown in Figure 20-32, some .NET Framework classes are thread-safe for reading, but not for writing.

click to expand
Figure 20-32: The thread safety notice for the Dataset class

Figure 20-33 displays a class that is generally not thread-safe. However, it offers a thread-safe alternative through a specified SyncRoot property.

click to expand
Figure 20-33: The thread safety notice for the Array class

As shown in Figure 20-34, you can use a specified thread-safe property to synchronize access to an otherwise not-thread-safe class.

click to expand
Figure 20-34: The thread safety notice for the Array.SyncRoot property

As shown in Figure 20-35, some .NET Framework classes offer limited thread safety while also offering a thread-safe wrapper class.

click to expand
Figure 20-35: The thread safety notice for the ArrayList class

Finally, in Figure 20-36, a wrapper method is available on several .NET classes for thread-safe operations. It appears that the majority of the classes found in the System.Collections namespace offer this same wrapper method alternative.

click to expand
Figure 20-36: The thread safety notice for the wrapper method ArrayList.Synchronized

This series of figures should give you a good idea of the range of thread safety support that exists across the .NET Framework. Being aware is really part of the challenge. Once you approach multithreading from this perspective, your chances for success increase dramatically.

Let's turn now to look at some actual multithreading code. That is, if you are still interested in multithreading.

Caution  

One last warning: Incorrect use of multithreading can be hazardous to your career! In other words, be extremely careful. Give serious consideration to extensive study and research using sample applications only. Make sure you have a firm understanding of multi- threading before you implement any explicit use of multithreading in your production applications. What you read in this chapter is simply an introduction to the topic of multithreading. As you can see, multi- threading does involve a low-level understanding about your .NET application. This is cause for a cautious and respectful approach. Yes, explore. But be careful.

Using Multithreading

As you have gathered by now, the .NET Framework provides a namespace for the Threading classes. The namespace, System.Threading, offers a vast collection [13] of classes and several delegates, enumerations, and structures. To provide a general idea of how to use two common System.Threading classes ( Thread and ThreadStart ), I have created a sample .NET Windows application called MyThreadingExampleVB.

This sample application presents a Windows Form containing four Button controls, one Label control, and one TextBox control. Logic located in the Form class constructor method ( New ) executes once automatically when the sample application is loaded. At that time, the TextBox is updated with information reflecting thread statistics. Otherwise, further processing requires that you click either of the Button controls. From the top down, the buttons (when clicked) will expose the following functionality:

  • Explicitly create and start a background worker thread. A loop will begin. Each iteration through the loop will update the Label control with an incremented number.

  • Put the background worker thread to sleep.

  • Kill the process.

  • Attempt to run the loop in the main/primary thread. This will cause the sample application to "hang" and become unresponsive . This button is provided simply for demonstration purposes.

The design-time view in the VS .NET IDE of the sample application MyThreadingExampleVB is shown in Figure 20-37.

click to expand
Figure 20-37: The sample .NET Windows application MyThreadingExampleVB

The code in the Form class constructor (New) method is shown in Listing 20-6.

Listing 20-6: The Form Class Constructor Method (New) of the MyThreadingExampleVB Sample Application
start example
 Public Class Form1     Inherits System.Windows.Forms.Form #Region " Windows Form Designer generated code " Public Sub New()        MyBase.New()           'This call is required by the Windows Form Designer.        InitializeComponent()           'Add any initialization after the InitializeComponent() call       System.Threading.Thread.CurrentThread.Name = "MyPrimaryThread"       Dim myappdomain As AppDomain       myappdomain = AppDomain.CurrentDomain       TextBox1.Text = "AppDomain: " & _       myappdomain.ToString & vbCrLf       TextBox1.Text = TextBox1.Text & _       "Current Executing Thread: " & _       myappdomain.GetCurrentThreadId.ToString & vbCrLf       'TextBox1.Text = TextBox1.Text & "Context: " & _       'System.Threading.Thread.CurrentContext.DefaultContext.ToString & vbCrLf           Dim p As System.Diagnostics.Process = Process.GetCurrentProcess()       Dim ProcessThreadArray As System.Diagnostics.ProcessThread       TextBox1.Text = TextBox1.Text & "Name: " & _       System.Threading.Thread.CurrentThread.Name & vbCrLf           For Each ProcessThreadArray In p.Threads             TextBox1.Text = TextBox1.Text & _             "------------------------------" & vbCrLf             TextBox1.Text = TextBox1.Text & _                "Thread ID: " & ProcessThreadArray.Id.ToString & vbCrLf             TextBox1.Text = TextBox1.Text & _          "PriorityLevel :" & ProcessThreadArray.PriorityLevel.ToString & vbCrLf             TextBox1.Text = TextBox1.Text & _             "ThreadState: " & ProcessThreadArray.ThreadState.ToString & vbCrLf       Next End Sub . . . 
end example
 

You will notice the System.Threading namespace used in several places in Listing 20-6. Additionally, notice the following code line:

 System.Threading.Thread.CurrentThread.Name = "MyPrimaryThread" 

Basically, that line is assigning a name to the main/primary thread. You should also pay particular attention (in the code in Listing 20-6) to the use of the System.Diagnostics.Process class. This is the key to getting a collection of the available threads.

Now let's look at the remaining code used in the sample application. The code in Listing 20-7 is taken from the Click events/methods for each respective Button control.

Listing 20-7: The Remaining Code from the MyThreadingExampleVB Sample Application
start example
 Public Class Form1     Inherits System.Windows.Forms.Form #Region " Windows Form Designer generated code " . . . (the relevant portions of code left out here already shown above) #End Region           Dim myThreadStart As New _       System.Threading.ThreadStart(AddressOf ThreadingDelegateMethod)       Dim mythread As New System.Threading.Thread(myThreadStart)       Dim I As Int32           Private Sub Button1_Click_1(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles Button1.Click            Button1.Enabled = False            Button4.Enabled = False            Call ThreadingDemo()       End Sub           Private Sub Button2_Click(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles Button2.Click            mythread.Sleep(2000)       End Sub           Private Sub ThreadingDemo()            mythread.Name = "MyBackGroundThread"            mythread.Priority = Threading.ThreadPriority.BelowNormal            mythread.Start()       End Sub           Public Sub ThreadingDelegateMethod()            Dim myappdomain As AppDomain            myappdomain = AppDomain.CurrentDomain                Dim p2 As System.Diagnostics.Process = Process.GetCurrentProcess()            Dim ProcessThreadArray2 As System.Diagnostics.ProcessThread            TextBox1.Text = TextBox1.Text & _            "********************************" & vbCrLf            TextBox1.Text = TextBox1.Text & _            "Current Executing Thread from AppDomain: " & _            myappdomain.GetCurrentThreadId.ToString & vbCrLf            TextBox1.Text = TextBox1.Text & "Name: " & _            System.Threading.Thread.CurrentThread.Name & vbCrLf            For Each ProcessThreadArray2 In p2.Threads                  TextBox1.Text = TextBox1.Text & _                 "------------------------------" & vbCrLf                 TextBox1.Text = TextBox1.Text & "Thread ID: " & _                 ProcessThreadArray2.Id.ToString & vbCrLf                 TextBox1.Text = TextBox1.Text & "PriorityLevel :" & _                 ProcessThreadArray2.PriorityLevel.ToString & vbCrLf                 TextBox1.Text = TextBox1.Text & "ThreadState: " & _                 ProcessThreadArray2.ThreadState.ToString & vbCrLf            Next                Do While True                  I += 1                  Dim myMethodInvoker As New MethodInvoker(AddressOf updateLabel)                  myMethodInvoker.Invoke()                  'Call updateLabel()             Loop       End Sub       Public Sub updateLabel()            Label1.Text = "!! The Thread is Running !! " & i       End Sub       Private Sub Button3_Click(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles Button3.Click            'one way to kill a process            'Dim p3 As System.Diagnostics.Process = _            'Process.GetCurrentProcess()            'p3.Kill()                'Another way to "kill" a process            System.Environment.Exit(0)           End Sub           Private Sub Button4_Click(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles Button4.Click            'This will block the primary thread            Call ThreadingDelegateMethod()       End Sub     End Class 
end example
 

Observe that near the top of Listing 20-7 are a few lines of code that look like this:

 Dim myThreadStart As New _ System.Threading.ThreadStart(AddressOf ThreadingDelegateMethod) Dim mythread As New System.Threading.Thread(myThreadStart) 

These few lines begin the creation of the worker thread. Notice the System.Threading.ThreadStart delegate being instantiated . Additionally, you should note the System.Threading.Thread class being instantiated. The remaining logic to execute (start) the new worker thread is a third line of code that uses the Start method. You will see that when the Button1_Click_1 event/method is exe cuted, the worker thread is given the name MyBackGroundThread and a priority of Threading.ThreadPriority.BelowNormal . Then, the Start method is executed to start the new worked thread.

As per the ThreadStart delegate's instruction, ThreadingDelegateMethod is executed as the worker thread starts. The worker thread will continue to run until you kill the process (using the appropriate Button control). By the way, the BelowNormal priority was chosen to ensure that you would still be able to interact with the GUI Windows Form. After all, the main/primary thread will continue to run. You are dependent on the main/primary thread allowing you to continue interacting with the application while the worker thread runs in the background.

Tip  

In reference to the preceding sample code, notice the use of the MethodInvoker delegate to invoke the updateLabel subprocedure. Reportedly, this is a thread-safe approach when you use one thread to update GUI Windows Form controls that were created by a different thread (rather than just calling the updateLabel subprocedure). In this case, the main/primary thread created the controls.

Run the sample application. Before you click any of the Button controls, the sample application reflects the available information as expected (see Figure 20-38). At this point, the worker thread has not been started.

click to expand
Figure 20-38: Executing the sample .NET Windows application MyThreadingExampleVB. The worker thread has not been started.

The information captured for the scrollable TextBox control is shown in Listing 20-8.

Listing 20-8: After Starting the MyThreadingExampleVB Sample Application, the Information As Written to the TextBox Control Before the Worker Thread Is Started
start example
 AppDomain: Name: MyThreadingExampleVB.exe There are no context policies.     Current Executing Thread: 3780 Name: MyPrimaryThread ------------------------------ Thread ID: 3780 PriorityLevel :Normal ThreadState: Running ------------------------------ Thread ID: 2968 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 3844 PriorityLevel :Highest ThreadState: Wait ------------------------------ Thread ID: 3372 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 2840 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 3900 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 3148 PriorityLevel :Normal ThreadState: Wait 
end example
 

Next, click the Button control at the top of the form to start the worker thread (see Figure 20-39).

click to expand
Figure 20-39: Executing the sample .NET Windows application MyThreadingExampleVB. The worker thread has been started. Notice that the Label control is being updated.

After the worker thread is started, additional information is concatenated and written to the scrollable TextBox control. The Label control reflects the incre menting value. Experiment with the button to put the worker thread to sleep. You will notice that the worker thread wakes up after the code for 2,000 milliseconds . Next, click the appropriate button to kill the process. Listing 20-9 shows the addi tional information captured from the TextBox control.

Listing 20-9: After Starting the Worker Thread, Additional Information Is Captured from the TextBox Control
start example
 ******************************** Current Executing Thread from AppDomain: 2312 Name: MyBackGroundThread ------------------------------ Thread ID: 3780 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 2968 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 3844 PriorityLevel :Highest ThreadState: Wait ------------------------------ Thread ID: 3372 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 2840 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 3900 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 3148 PriorityLevel :Normal ThreadState: Wait ------------------------------ Thread ID: 2312 PriorityLevel :BelowNormal ThreadState: Running 
end example
 

As you can see from Listing 20-9, the worker thread (which has an ID of 2312) has a ThreadState of Running. The main/primary thread (which has an ID of 3780) has a ThreadState of Wait. Each time you execute this sample application, the thread ID value will change. However, the relative results will be the same.

If you want to see what will happen when the main/primary thread is allowed to run the loop, simply click the appropriate button (associated with the Button4_Click event/method). However, let me warn you: After you click this button, you will not be able to interact with the Windows Form ”not even to kill the process. You will notice that the Label control does not reflect the incre mented counter. To kill the process, you will need to press Ctrl-Alt-Delete. Locate and select the MyThreadingExampleVB sample application/process in the Windows Task Manager window (either on the Processes or Applications tab). On the Applications tab, click End Task. Optionally , on the Processes tab, click End Process.

As you can see, writing out information is useful, specifically for learning and demonstration purposes. During your real-life development, it is likely that you will want incorporate either of the tools discussed in the next section.

Tools to Help Monitor Your Application

During the development of your multithreaded application, rather than writing information out to a TextBox control, you may find it more useful to take advantage of one of the .NET Framework classes that are designed specifically for the purpose of writing out information to help in debugging scenarios. You have seen the System.Diagnostics.Trace class before. The System.Diagnostics.Debug class is another very useful .NET Framework class. I suggest that you become familiar with both as each has its advantages.

Now, there are times when you will want to use other techniques or other tools to diagnose and troubleshoot your applications. This holds true even when you are not working with multithreaded applications. Nevertheless, this is a conve nient point at which to bring the topic up.

In the next sections you'll take a look at the following tools:

  • The Windows Performance Monitor (Perfmon) tool

  • The VS .NET IDE Threads window

  • The VS .NET IDE Disassembly window

The Performance Monitor

You may recall reading about the Performance Monitor (Perfmon) in an earlier chapter. Yes, this is the same tool that I introduced to you back in Chapter 8. Recall that you can access this tool by navigating to your desktop taskbar, clicking the Start button, and selecting Programs Administrative Tools Performance. Because you have seen this tool before, I do not go into too much detail regarding its use in this section.

Start by executing the MyThreadingExampleVB sample and clicking the appropriate button control to start the worker thread. Then, while the Label control is reflecting an active background worker thread, launch the Perfmon tool.

As you have seen before, Figure 20-40 shows the Add Counters window. Select Thread from the Performance object drop-down box. Then select the Context Switches/sec counter. Notice that the "Select instances from list" selection box shows an instance available for each thread that the sample application has. Select each available instance for the sample application and click Add to add your selected counters and instances.

click to expand
Figure 20-40: Adding performance counters

As shown in Figure 20-41, you can access the Explain Text window by clicking Explain on the Add Counters window. Click Close to close the Add Counters window.

click to expand
Figure 20-41: The Explain Text window

The Performance window (see Figure 20-42) shows that there is significant activity for a couple of the selected instances.

click to expand
Figure 20-42: The Performance window showing activity for the sample application

Next, return to the Add Counters window. This time, select All counters for the Thread performance object (see Figure 20-43). Click Add and then click Close to close the Add Counters window.

click to expand
Figure 20-43: Adding all counters for the Thread performance object

As shown in Figure 20-44, adding all of the counters gives you much more information about the thread activity of the sample application.

click to expand
Figure 20-44: The Performance window showing more activity for the sample application

After you explore this displayed information, you are able to map each instance number with a specific thread ID. You can even map the thread ID values shown here with the thread ID values written to the MyThreadingExampleVB Windows Form TextBox display. Experiment with this tool. While you look at the performance graphs, click the appropriate button on the MyThreadingExampleVB sample application to put the worker thread to sleep. Notice the change in the per formance display.

Using the Perfmon tool, you can see that there are context switches occurring. In some real-life production cases, this could be a cause for concern. Otherwise, from this Perfmon tool display, you can see that while the background worker thread is active, the main/primary thread continues to be active as well. This is an important point to realize. Because you have not coded otherwise, the operating system continues to switch in and out each thread, giving each a chance to process.

For now, kill the sample application process in preparation for the next topic.

The VS .NET Threads Window

The VS .NET IDE offers several useful debug windows. One window that is partic ularly applicable to this multithreading discussion is the Threads window.

To prepare for the next demonstration, kill the sample application process (if you have not already done so). Then, with the MyThreadingExampleVB project open in VS .NET, place a debug breakpoint in the ThreadingDelegateMethod subprocedure as shown in Figure 20-45.

click to expand
Figure 20-45: Placing a debug breakpoint

Start the MyThreadingExampleVB sample application from within VS .NET by pressing F5 (optionally, you can click the Start arrow located on the VS .NET IDE Standard toolbar). After you click the appropriate button to start the worker thread, processing will pause at your breakpoint. Once execution is paused (in break mode), navigate to the main VS .NET IDE toolbar and select Debug Windows Threads (optionally, press Ctrl-Alt-H), as shown in Figure 20-46.

click to expand
Figure 20-46: Launching the VS .NET Threads window

As shown in Figure 20-47, the Threads window provides a very quick, code-free way to view threading information.

click to expand
Figure 20-47: Viewing the VS .NET Threads window

Additionally, if you right-click inside of the Threads window, you will see a pop-up context window. You can use this pop-up context window for minimal thread manipulation. Use this sample application to safely explore and learn.

You can resume the processing of the sample application by pressing F5 (optionally, you can click the Continue arrow located on the VS .NET IDE Debug toolbar). By default, you can press the F11 key if you want to step through the code, line by line. There are many occasions when stepping through code interactively is useful. Those of you who have had the opportunity to use interactive debuggers on the mainframe (e.g., INTERTEST, XPEDITER, COBTEST, or even TESTCOB) will certainly feel right at home using these VS .NET debugging features.

Note  

If you leave execution paused long enough, you will need to restart the sample application. One of the "other" background threads will expire. You might consider experimenting to see if you can get around this. In the meantime, just be aware of this fact. Once you have had a chance to view the Threads window, promptly press F5 to resume execution.

Let's now take a look at another tool within the VS .NET IDE.

The VS .NET Disassembly Window

Following the same steps to place the sample application in break mode, navigate to the main VS .NET toolbar. This time, select Debug Windows Disassembly (optionally, press Ctrl-Alt-D). The VS .NET Disassembly window is shown in Figure 20-48.

click to expand
Figure 20-48: The VS .NET Disassembly window

While it's in break mode, the Disassembly window positions its display at the line of execution where the breakpoint was enabled. Notice that the source code is displayed along with the assembler language for the source code. Now, any main frame programmer would have to feel right home after using this VS .NET tool. Recall that pesky mainframe COBOL bug that I mentioned to you earlier (in the section "A Bug to Remember")? Certainly, if you ever really want or need to know what lower-level code is actually running, the Disassembly window is as close as a couple of VS .NET clicks.

Can you believe it? Interactive debugging with assembler language code is available right there in your VS .NET IDE. What more could you ask for? Can it get any better? Actually, yes, it does get even better. Any programmer inclined to look at assembler language code will certainly want to view the contents of the registers as well as the actual memory content.

Using the now-familiar VS .NET navigation path , you can launch the Registers window by selecting Debug Windows Registers (optionally, press Ctrl-Alt-G), as shown in Figure 20-49.

click to expand
Figure 20-49: Viewing the VS .NET Registers window

You can launch one or more (up to four) Memory windows by selecting Debug Windows Memory Memory ? (optionally, press Ctrl-Alt-M- ? ). You replace ? with 1, 2, 3, or 4 depending on how many Memory windows you wish to open. See Figure 20-50.

click to expand
Figure 20-50: Viewing a VS .NET Memory window

You and I know that there have been times when you needed this type of low- level information. Practically every seasoned mainframe programmer has had those occasions of needing to view memory dumps. Granted, it's not an everyday occasion. At the same time, it's not an everyday occasion that you'll find yourself needing to build multithreaded applications, either.

start sidebar
One Last Shameless Plug

You can use this book to serve as your portal, your guide to your real-life .NET retraining journey. There remains one question though. What are your coworkers, colleagues, friends , and family ”your mainframe brothers and sisters ” using for their .NET guide? What are they using as their bridge into the .NET world? Sure, you are well on your way. Let's remember the others. Please consider obtaining extra copies of this book to share, to spread the word. I do think that others ”those with mainframe COBOL/CICS backgrounds such as ours ”will appreciate receiving a copy of this book as a gift.

end sidebar
 

[13] Sorry, there are no interfaces in this namespace. There goes my chance to spell out one of my favorite acronyms: DICES.




COBOL and Visual Basic on .NET
COBOL and Visual Basic on .NET: A Guide for the Reformed Mainframe Programmer
ISBN: 1590590481
EAN: 2147483647
Year: 2003
Pages: 204

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net