Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
AccessAdobe photoshopAlgoritmiAutocadBaze de dateCC sharp
CalculatoareCorel drawDot netExcelFox proFrontpageHardware
HtmlInternetJavaLinuxMatlabMs dosPascal
PhpPower pointRetele calculatoareSqlTutorialsWebdesignWindows
WordXml

AspAutocadCDot netExcelFox proHtmlJava
LinuxMathcadPhotoshopPhpSqlVisual studioWindowsXml

Threading in .NET

c



+ Font mai mare | - Font mai mic



Threading in .NET

Overview

In Chapter 1 we described what threading is. We covered a lot of the common ground that many may be familiar with already. Knowing the what portion of threading is important. In this chapter, you will see how to implement some basic threading; however, it is of equal, if not greater importance, to understand when to use threading.



By the end of this chapter, you will understand:

The System.Threading namespace

What design issues there are in the use of threads

What resources are used by threads

What are good opportunities for threading

What mistakes to avoid when using threads

System.Threading Namespace

We have already mentioned that threads in managed code are represented by a System.Threading.Thread class instance. In this section, we will discuss the System.Threading namespace in depth, as well as its contents. The classes available in the System.Threading namespace are listed in the following table.

Class

Description

AutoResetEvent

This event notifies one or more waiting threads that an event has occurred.

Interlocked

This class protects against errors by providing atomic operations for variables that are shared by multiple threads.

ManualResetEvent

This event occurs when notifying one or more waiting threads that an event has occurred.

Monitor

This class provides a mechanism that synchronizes access to objects.

Mutex

A synchronization primitive that grants exclusive access to a shared resource to only one thread. It can also be used for inter-process synchronization.

ReaderWriterLock

This class defines a lock that allows single-writer and multiple-reader semantics.

RegisteredWaitHandle

This class represents a handle that has been registered when calling the RegisterWaitForSingleObject() method.

SynchronizationLockException

This exception is thrown when a synchronized method is invoked from an unsynchronized block of code.

Thread

This class creates and controls a thread, sets its priority, and gets its status.

ThreadAbortException

This exception is thrown when a call is made to the Abort() method.

ThreadExceptionEventArgs

This class provides data for the ThreadException event.

ThreadInterruptedException

This exception is thrown when a thread is interrupted while it is in a waiting state.

ThreadPool

This class provides a pool of threads that can be used to post work items, process asynchronous I/O, wait on behalf of other threads, and process timers.

ThreadStateException

This is the exception that is thrown when a thread is in an invalid state for the method call.

Timeout

This class simply contains a constant integer used when we want to specify an infinite amount of time.

Timer

This class provides a mechanism for executing methods at specified intervals.

WaitHandle

This class encapsulates operating system-specific objects that wait for exclusive access to shared resources.

We won't use all of these classes in this section, but it's useful to understand what this namespace makes available to us. The other classes will be discussed in later chapters.

Thread Class

Right now, we are going to focus on the Thread class, since this class represents our processing threads. This class allows us to do everything, from managing a thread's priority, to reading its status.

Let's start by looking at a table of this class's public methods.

Public Method Name

Description

Abort()

This overloaded method raises a ThreadAbortException in the thread on which it is invoked, to begin the process of terminating the thread. Calling this method usually terminates the thread.

AllocateDataSlot()

This static method allocates an unnamed data slot on all the threads.

AllocateNamedDataSlot()

This static method allocates a named data slot on all threads.

FreeNamedDataSlot()

This static method frees a previously allocated named data slot.

GetData()

This static method retrieves the value from the specified slot on the current thread, within the current thread's current domain.

GetDomain()

This static method returns the current domain in which the current thread is running.

GetDomainID()

This static method returns a unique application domain identifier.

GetHashCode()

This method serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures like a hash table.

GetNamedDataSlot()

This static method looks up a named data slot.

Interrupt()

This method interrupts a thread that is in the WaitSleepJoin thread state.

Join()

This overloaded method blocks the calling thread until a thread terminates.

ResetAbort()

This static method cancels an Abort() requested for the current thread.

Resume()

This method resumes a thread that has been suspended.

SetData()

This static method sets the data in the specified slot on the currently running thread, for that thread's current domain.

Sleep()

This static and overloaded method blocks the current thread for the specified number of milliseconds.

SpinWait()

This static method causes a thread to wait the number of times defined by the iterations parameter.

Start()

This method causes the operating system to change the state of the current instance to ThreadState.Running.

Suspend()

This method will either suspend the thread, or if the thread is already suspended, has no effect.

Now let's look at another table, this time containing its public properties.

Public Property Name

Description

ApartmentState

Sets or gets the apartment state of this thread.

CurrentContext

This static property gets the current context in which the thread is executing.

CurrentCulture

Sets or gets the culture for the current thread.

CurrentPrincipal

This static property sets or gets the thread's current principal. It is used for role-based security.

CurrentThread

This static property gets the currently running thread.

CurrentUICulture

Used at run time, this property sets or gets the current culture used by the Resource Manager to look up culture-specific resources.

IsAlive

Gets a value that indicates the execution status of the current thread.

IsBackground

Sets or gets a value that indicates whether a thread is a background thread or not.

IsThreadPoolThread

Gets a value indicating whether a thread is part of a thread pool.

Name

Sets or gets the name of the thread.

Priority

Sets or gets a value that indicates the scheduling priority of a thread.

ThreadState

Gets a value that contains the states of the current thread.

Again, we won't use all of these properties and methods in this chapter. We've seen these class members, but it does us little good until we can at least create a thread - or a reference to one. So let's get our feet wet with a simple C# threading example.

Creating a Thread

We are going to use a simple example here. This isn't a good example of why you should use a new thread but it strips off all of the complexities that will be covered later. Create a new console application with a file called simple_thread.cs and place the following code in it:

using System;
using System.Threading;

public class SimpleThread


public static void Main()


Now save, compile, and execute the file. Your output should look something like this:

This code calculated the value 50 from thread id: 1400
This code calculated the value 50 from thread id: 1040

Let's walk through this simple example and make sure we understand what is happening here. As we have already established, the threading functionality is encapsulated in the System.Threading namespace. As such, we must first import this namespace into our project. Once the namespace is imported, we want to create a method that can be executed on the main (primary) thread and on our new worker thread. We use SimpleMethod() in our example:

public void SimpleMethod()

As you can see, we are using the AppDomain class that we introduced in Chapter 1 to find out what thread we are running on. This method, whenever it is executed, simply does a sum, and prints the result, along with a report of which thread the calculation was performed on.

Our program's entry point is the Main method. The first thing we do inside this method is execute our SimpleMethod() method. This calls the method on the same thread as that on which the Main method is running. The next part is important: we get our first look at creating a thread. Before we can create a thread in C#, we must first create a ThreadStart delegate instance. A delegate is really an object-oriented type-safe function pointer. Since we are going to tell a thread what function to execute, we are essentially passing a function pointer to the thread's constructor. This is demonstrated in our application as follows:

ThreadStart ts = new ThreadStart(simpleThread.SimpleMethod);

One thing to notice is that the method name is not accompanied by parentheses; it simply takes the method's name. Once we have created our ThreadStart delegate, we can then create our Thread for execution. The only constructor for a Thread takes an instance of the ThreadStart delegate. We again demonstrated this in our code with the following line:

Thread t = new Thread(ts);

We are declaring a variable called t as a new Thread. The Thread class constructor takes the ThreadStart delegate as its sole parameter.

On our next line we call the Start() method of the Thread object. This starts off a new execution thread, which begins by invoking the ThreadStart delegate we passed into the constructor, which in turn invokes the method. We follow this up with Console.ReadLine() so the program will wait on your key input before exiting our main thread:

t.Start();
Console.ReadLine();

When the method is executed this second time, we can see that the code is indeed executing on a different thread.

OK, so we've created a thread, but that doesn't really provide any insight into the power of threads. The fact that we are displaying different thread IDs doesn't really do much - we haven't executed more than one thing at once yet. To see how we can use this same threading code in a more realistic application, we are going to create another program that simulates a long process executing in the background while another process executes in the foreground. Create a new console application and place this code in a new file called do_something_thread.cs:

using System;
using System.Threading;

public class DoSomethingThread



static void Main()


Console.ReadLine();


Your output may be somewhat different every time. The thread execution will be switched at different points in the loop every time. But your concatenated results will look something like this:

Primary Thread: 1
Primary Thread: 2
Primary Thread: 3

Worker Thread: 743
Worker Thread: 744
Worker Thread: 745

Primary Thread: 1000

We won't walk through this code because it doesn't introduce any new coding techniques. However, as we can see, execution time is shared between the two threads. Neither thread is completely blocked until the other finishes. Instead, each thread is given a small amount of time to execute. After one thread has run out of execution time, the next thread begins executing in its time slice. Both threads continue to alternate until execution is completed. Actually, there are more than just our two threads that are alternating and sharing time slices. We aren't just switching between the two threads in our application. In reality, we are sharing our execution time with many other threads currently running on our computer.

ThreadStart and Execution Branching

Take a look, once again, at the ThreadStart delegate we mentioned earlier. We can do some interesting work with these delegates. Let's examine a quick example in a real-world scenario. Suppose that you want to perform some background routine when a user launches an application. Depending on who is launching the application, you want to perform different routines. For instance, let's say that when an administrator logs into an application, you want to run a background process that will gather report data and format it. That background process will alert the administrator when the report is available. You probably wouldn't want to perform the same reporting function for an ordinary user as you would for an administrator. This is where the object-oriented nature of ThreadStart is useful.

Let's look at some example code. We aren't going to code the exact scenario described above, but we will show you how you can branch based on a certain criteria defined in a ThreadStart. Create a new console application and place the following code in a file called ThreadStartBranching.cs:

using System;
using System.Threading;

public class ThreadStartBranching


static void AdminMethod()


static void UserMethod()


static void ExecuteFor(UserClass uc)


static void Main()



The output from the code is quite simple:

Admin Method
User Method

We will detail some of the important points to observe here. First, you will notice that we created an enumeration of the types of user that may be executing code:

enum UserClass

The next thing you'll notice is that we created two methods: AdminMethod() and UserMethod(). These would theoretically execute a long series of instructions that would be completely different for the two different user types. In our case, we just want to identify that they have run so we write them out to the console:

static void AdminMethod()


static void UserMethod()

The next thing you'll notice is that within the ExecuteFor() method we declared a variable called ts as a ThreadStart class, but didn't create an instance with the New keyword. We then created two new ThreadStart objects that point to the different methods created above:

ThreadStart ts;
ThreadStart tsAdmin = new ThreadStart(AdminMethod);
ThreadStart tsUser = new ThreadStart(UserMethod);

So, now we have two new ThreadStart objects and a variable that can hold an instance of a ThreadStart. Then we branch our code with an If statement and set our empty ts variable to the instance of the ThreadStart that coincides with our business rule:

if(uc == UserClass.ClassAdmin)
ts = tsAdmin;
else
ts = tsUser;

Lastly, we pass the dynamically assigned ThreadStart delegate to our Thread constructor to create a thread, and begin its execution:

Thread t = new Thread(ts);
t.Start();

Thread Properties and Methods

As we showed in the beginning of this chapter, there are many properties and methods of the Thread class. We promised that controlling the execution of threads was made much simpler with the System.Threading namespace. So far, all we have done is create threads and start them.

Let's look at two more members of the Thread class; the Sleep() method and the IsAlive property. In Chapter 1 we said that a thread may go to sleep for a time until it is clock-interrupted. Putting a thread to sleep is as simple as calling the static Sleep() method. We also stated that we could determine a thread's state. In the following example we are going to use the IsAlive property to determine if a thread has completed its executions, and the Sleep() method to pause the execution of a thread. Look at the following code, thread_sleep.cs, where we will make use of both of these members:

using System;
using System.Threading;

public class ThreadState


Console.WriteLine('Worker Function Complete');


static void Main()


ThreadState = t.ThreadState.ToString();
Console.WriteLine('He's finally done! Thread state is: '
+ ThreadState);
Console.ReadLine();


Your output should look similar to the following (try experimenting with the values in the for loop and passed to the sleep() method to see different results):

Still waiting. I'm going back to sleep.
Worker: Running
Worker: Running
Worker: Running
Worker: Running
Worker: Running
Worker: Running
Worker: Running
Worker: Running
Worker: Running
Worker: Running
Worker Function Complete
He's finally done! Thread state is: Stopped

Let's look at the Main method where we have used our new concepts First, we create a thread and pass it the method we want to execute as a delegate:

Thread t = new Thread(new ThreadStart(WorkerFunction));
t.Start();

Notice that instead of creating a variable to hold our ThreadStart class, we simply created one on the fly and passed it as the parameter of our Thread constructor. As usual, our Main method continues to execute alongside our new thread as the processor switches between them. Then we use the IsAlive property of our newly created thread to see if it is still executing. We will continue to test this variable. While the worker thread is alive, the main thread will continue to sleep for 200 milliseconds, wake up the thread, and test if our worker thread is still alive again:

while(t.IsAlive)


Next we want to look at the ThreadState property that we have used twice in our code. The ThreadState property is actually a property that returns an enumerated type. The enumeration tells you exactly what state the thread is in. We can either test this property with an if statement as we did in our last example or use the ToString() method on the property and write out its state in text form:

ThreadState = t.ThreadState.ToString();
Console.WriteLine('He's finally done! Thread state is: '
+ ThreadState);

The rest of this code is standard and doesn't need to be reviewed. There are some important things to note. The first is that we tell one thread to sleep for a specified period so that we yield execution to our other threads. We do that with the Thread object's Sleep() method - passing in the length of time in milliseconds that we want to the thread to sleep. In addition, we can test our threads to see if they have finished executing by using the IsAlive property. Lastly, we can use the ThreadState property of our thread instances to determine their exact thread state.

Thread Priorities

The thread priority determines the relative priority of the threads against each other. The ThreadPriority enumeration defines the possible values for setting a thread's priority. The available values are:

Highest

AboveNormal

Normal

BelowNormal

Lowest

When a thread is created by the runtime and it has not been assigned any priority then it will initially have the Normal priority. However, this can be changed using the ThreadPriority enumeration. Before seeing an example for the thread priority, let's see what a thread priority looks like. Let's create a simple threading example that just displays the name, state, and the priority information about the current thread, thread_priority.cs:

using System;
using System.Threading;

public class ThreadPriority


public static void FindPriority()


There is a simple method called FindPriority() that displays the name, state, and priority information of the current thread, which produces output like the following:

Entering the void Main()
Exiting the void Main()
Name: FindPriority() Thread
State: Running
Priority: Normal

We know the worker thread is running with a Normal priority. Let's add a new thread, and call our reporting method with a different priority. Here's thread_priority2.cs:

using System;
using System.Threading;

public class ThreadPriority2


static public void FindPriority()


}

The output from thread_priority2.cs will be something like the following:

Entering void Main()
Name: FindPriority() Thread2
State: Running
Priority: Highest
Exiting void Main()
Name: FindPriority() Thread
State: Running
Priority: Normal

Threads are scheduled for execution based on the priority set using the Priority property. Every operating system will execute a thread priority differently and the operating system could change the priority of the thread.

There is no way that our application can restrict the operating system from changing the priority of the thread that was assigned by the developer, since the OS is the master of all threads and it knows when and how to schedule them. For example, the priority of the thread could be dynamically changed by the OS due to several factors, such as system events like user input that has higher priority, or lack of memory that will trigger the garbage-collection process.

Timers and Callbacks

We've seen some simple examples of threading. What we haven't covered at all is the issue of synchronization, although we will cover that in much greater detail in the next chapter. As threads run out of sequence from the rest of the application code, we cannot be certain that actions affecting a particular shared resource that occur in one thread will be completed before code in another thread wants to access that same shared resource. There are various methods of dealing with these issues, but here we will cover one simple way; the use of timers. Using a timer, we can specify that a method is executed at a specific regular interval, and this method could check that the required actions have been completed before continuing. This is a very simple model, but can apply to a variety of situations.

Timers are made up of two objects, a TimerCallback and a Timer. The TimerCallback delegate defines the method to be called at a specified interval, whereas the Timer is the timer itself. The TimerCallback associates a specific method with the timer. The Timer's constructor (which is overloaded) requires four arguments. The first is the TimerCallback specified earlier. The second is an object that can be used to transmit state across to the method specified. The last two arguments are the period after which to start periodic method calls, and the interval between subsequent TimerCallback method calls. They can be entered as integers or longs representing numbers of milliseconds, but as you will see below, an alternative is to use the System.TimeSpan object with which you can specify the intervals in ticks, milliseconds, seconds, minutes, hours, or days.

The easiest way to show how this works is by demonstration, so below we will detail an application that fires two threads. The second thread will not perform its operations until the first has completed its operations; thread_timer.cs:

using System;
using System.Threading;
using System.Text;

public class TimerExample
while(true);
Console.WriteLine('Exiting Main');
Console.ReadLine();


The above loop just freezes the Main loop until the complete field is true. In a GUI different methods could be used, as the Application.Run() method puts the application in a perpetual loop anyway:

public void GenerateText()


message = sb.ToString();

Above is the first method used, which just generates 200 lines of text using a StringBuilder object, and then stores them in the message field.

public void GetText(object state)

} // class

The last method used in this class is fired every two seconds by the timer. If message hasn't been set yet, then it exits; otherwise it outputs a message and then disposes of the timer. This stops the timer from continuing to count. This should be performed as soon as the timer is no longer necessary.

The output from thread_timer.cs will be as follows:

Message is :
This is Line 1
This is Line 2



This is Line 199
This is Line 200

Exiting Main

Spinning Threads with Threads

We've seen in code how to spawn a thread from the void Main(). In a similar way, we can also spawn multiple threads within a thread. For example, let's say we have a Car class that has a public method called StartTheEngine(). The StartTheEngine() method calls another three private methods called CheckTheBattery(), CheckForFuel(), and CheckTheEngine(). Since each of these tasks, checking the battery, fuel, and engine, can happen simultaneously, we can run each of these methods in a different thread. Here is how the Car class is implemented in thread_spinning.cs:

using System;
using System.Threading;

class Car

Console.WriteLine('Engine is ready!');


private void CheckTheBattery()

Console.WriteLine('Finished checking the Battery!');


private void CheckForFuel()

Console.WriteLine('Fuel is available!');


private void CheckTheEngine()

Console.WriteLine('Finished checking the engine!');


In the StartTheEngine() method, we create three threads and then start each of them one by one. Let's add an entry point to our class so we can see some results of our code:

public static void Main()

Console.WriteLine('Exiting void Main!');
Console.ReadLine();

In the void Main() method we simply create one more thread and execute the StartTheEngine() method in that thread, as illustrated in Figure 1.


Figure 1

The output should look something like the following:

Entering void Main!
Exiting void Main!
Starting the engine!
Checking the Battery!
Checking for Fuel!
Checking the engine!
Finished checking the Battery!
Fuel is available!
Finished checking the engine!
Engine is ready!

As you can see, each of these methods works in it's own thread and is executed in its own time-sliced slot.

Spinning Threads with Threads with Threads

We can split the Car class into separate classes and we could build two more methods in a new Engine class called check1() and check2(). Then the Engine class will execute the check1() and check2() methods in its own thread as shown in Figure 2.


Figure 2

We'll remove the CheckTheEngine() method from the Car class and create one more class called Engine; see thread_spinning2.cs:

using System;
using System.Threading;

class Engine

Console.WriteLine('Finished checking the engine!');


private void Check1()

Console.WriteLine('Finished engine check1!');


private void Check2()

Console.WriteLine('Finished engine check2!');


The Engine class has the public method CheckTheEngine() that creates two more threads and calls the check1() and check2() methods. Here is how the results may look:

Entering void Main!
Exiting void Main!
Starting the engine!
Checking the Battery!
Checking for Fuel!
Checking the engine!
Starting the engine check!!
Starting the engine check2!
Finished checking the Battery!
Fuel is available!
Engine is ready!
Finished engine check1!
Finished checking the engine!
Finished engine check2!

As you can see, spawning threads from within threads is very easy. However, you may be interested in knowing the disadvantages: as the number of active threads goes up, the performance degrades.

Performance Considerations

The more threads you create, the more work the system has to do to maintain the thread contexts and CPU instructions. The Processes tab of the Windows Task Manager will tell you how many processes and threads are currently running. However, these will be OS processes and they're not equivalent to the AppDomains. You can also look at the running threads while debugging a given .NET application by using the threads window.

If we want to know how many threads are running inside the CLR then you have to use the Windows Performance Monitor tool and add a couple of CLR-specific performance categories. The CLR exposes a performance counter category called .NET CLR LocksAndThreads and we can use this category to get more information about the CLR-managed threads. Let's run the Performance Monitor and add the counters shown in the following table from the .NET CLR LocksAndThreads category.

Performance Counter

Description

# of current logical Threads

This counter displays the number of current managed threads in the application and includes both the running and stopped threads.

# of current physical Threads

This counter displays the number of OS threads created and owned by the CLR. This counter may not map one to one with managed threads.

# of total recognized threads

This counter displays the number of current threads recognized by the CLR

Current Queue Length

This counter displays number of threads that are waiting to acquire locks in the managed application.

Total # of Contentions

This counter displays the number of failures when the managed applications try to acquire locks.

Here is how the values looks for our thread_spinning2 application:

Here is a comprehensive overview of the '.NET CLR LocksAndThreads' performance counter information.

The counter # of current local Threads specifies that 11 managed threads are created and owned by the CLR

Since we've added the counter instance '_Global_', we see all the threads created by the CLR.

The counter # of current physical Threads specifies that 8 OS threads are created and owned by the CLR

The counter # of total recognized Threads specifies that 3 OS threads are recognized by the CLR and they're created by the Thread object

The counter Total # of Contentions specifies that the runtime did not fail when it tried to acquire managed locks. Managed lock fails are bad for the execution of code

Lifecycle of Threads

When a thread is scheduled for execution it can go through several states, including unstarted, alive, sleeping, etc. The Thread class contains methods that allow you to start, stop, resume, abort, suspend, and join (wait for) a thread. We can find the current state of the thread using its ThreadState property, which will be one of the values specified in the ThreadState enumeration:

Aborted - The thread is in the stopped state, but did not necessarily complete execution

AbortRequested - The Abort() method has been called but the thread has not yet received the System.Threading.ThreadAbortexception that will try to terminate it - the thread is not stopped but soon will be.

Background - The thread is being executed in the background

Running - The thread has started and is not blocked

Stopped - The thread has completed all its instructions, and stopped

StopRequested - The thread is being requested to stop

Suspended - The thread has been suspended

SuspendRequested - The thread is being requested to suspend

Unstarted - The Start() method has not yet been called on the thread

WaitSleepJoin - The thread has been blocked by a call to Wait(), Sleep(), or Join()

Figure 3 shows the lifecycle of a thread. Figure 3.


Figure 3

In this section, we'll explore the lifecycle of threads.

Putting a Thread to Sleep

When we create a new thread we have to call the Start() method of the Thread object to schedule that thread. At this time, the CLR will allocate a time slice to the address of the method passed to the constructor of the Thread object. Once the thread is in the Running state, it can go back to either the Sleep or Abort states when the OS is processing the other threads. We can use the Sleep() method of the Thread class to put a thread to sleep. The Sleep() method is really useful if you are waiting for a resource and you want to retry for it. For example, let's say your application cannot proceed due to unavailability of a resource that it is trying to access. You may want your application to retry to access the resource after few milliseconds, in which case the Sleep() method is a good way to put the thread to sleep for a specified time before the application retries to access the resource.

The overloaded Sleep() method is available in two flavors. The first overload takes an integer as the parameter that will suspended the thread for number of milliseconds specified. For example, if you pass 100 to the parameter the thread will be suspended for milliseconds. This will place the thread into the WaitSleepJoin state. Let's see an example for this, thread_sleep2.cs:

using System;
using System.Threading;

public class ThreadSleep


public static void Counter()

Console.WriteLine();
Console.WriteLine('Exiting Counter');


public static void Counter2()

Console.WriteLine();
Console.WriteLine('Exiting Counter2');


The Counter() method counts numbers from 1 to 50 and when it reaches 10 it sleeps for 1000 milliseconds. The Counter2() method counts from 51 to 100 and when it reaches 70 it sleeps for 5000 milliseconds. Here is how the output might look:

Entering the void Main!
Entering Counter2
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 Exiting
the void Main!
Entering Counter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
50
Exiting Counter
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
94 95 96 97 98 99 100
Exiting Counter2

The second overload takes a TimeSpan as parameter and, based on the TimeSpan value, the current thread will be suspended. The TimeSpan is a structure defined in the System namespace. The TimeSpan structure has a few useful properties that return the time interval based on clock ticking. We can use public methods such as FromSeconds() and FromMinutes() to specify the sleep duration. Here is an example, thread_sleep3.cs:

public static void Counter()


}

public static void Counter2()


}

The output will be similar to that of thread_sleep2.

Interrupting a Thread

When a thread is put to sleep, the thread goes to the WaitSleepJoin state. If the thread is in the sleeping state the only way to wake the thread, before its timeout expires, is using the Interrupt() method. The Interrupt() method will place the thread back in the scheduling queue. Let's see an example for this, thread_interrupt.cs:

using System;
using System.Threading;

public class Interrupt

public static void SleepingThread()




public static void AwakeTheThread()




In the above example, the first thread (sleeper) is put to sleep when the counter reaches 10, 20, and 30. The second thread (worker) checks if the first thread is asleep. If so, it interrupts the first thread and places it back in the scheduler. The Interrupt() method is the best way to bring the sleeping thread back to life and you can use this functionality if the waiting for the resource is over and you want the thread to become alive. The output will look similar to the following:

Entering the Sub Main!
Exiting the Sub Main!
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
97 98 99 100 1 2 3 4 5 6 7 8 9 10 Going to sleep at: 10
11 12 13 14 15 16 17 18 19 20 Going to sleep at: 20
21 22 23 24 25 26 27 28 29 30 Going to sleep at: 30
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

Pausing and Resuming Threads

The Suspend() and Resume() methods of the Thread class can be used to suspend and resume the thread. The Suspend() method will suspend the current thread indefinitely until another thread wakes it up. When we call the Suspend() method, the thread will be place in the SuspendRequested or Suspended state.

Let's see an example for this. We'll create a new C# application that generates prime numbers in a new thread. This application will also have options to pause and resume the prime number generation thread. To make this happen let's create a new C# WinForms project called PrimeNumbers and build a UI like this in Form1.

We have a ListBox and three command buttons in the UI. The ListBox is used to display the prime numbers and three command buttons are used to start, pause, and resume the thread. Initially we've disabled the pause and the resume buttons, since they can't be used until the thread is started. Let's see what the code is going to look like. We've declared a class-level Thread object that is going to generated prime numbers.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;

namespace Chapter_02

All the Start button does is create a new Thread object with the ThreadStart delegate of the GeneratePrimeNumbers() method and assign the name Prime Number Example to the thread. Then it enables the Pause button and disables the Start button. Then it starts the prime number generating thread using the Start method of the Thread class.

Let's double-click on the Pause button and add the following code.

private void cmdPause Click(object sender, System.EventArgs e)


catch(ThreadStateException Ex)


The Pause button checks if the thread is in the Running state. If it is in the Running state, it pauses the thread by calling the Suspend method of the Thread object. Then it enables the Resume button and disables the Pause button. Since the Suspend method can raise the ThreadStateException exception, we're wrapping the code with in a trycatch block.

Double-click on the Resume button and add the following code.

private void cmdResume Click(object sender, System.EventArgs e)

catch(ThreadStateException Ex)



The Resume button checks if the state of the thread is Suspended or SuspendRequested before resuming the thread. If the state of the thread is either Suspended or SuspendRequested then it resumes the thread and disables the Resume button and enables the Pause button.

Well, so far our business logic is ready. Let's see the code that generates the prime numbers. Since our main aim is to use multithreading and not prime number generation, I'm not going to go deep into the code. The GeneratePrimeNumbers() method generates the first 255 prime numbers starting from 3. When the method finds a prime number it'll add the new prime number to an array as well as to the listbox. The first prime number, 2, will be automatically added to the listbox. Finally, the method will enable the Start button and disable the Pause button.

public void GeneratePrimeNumbers()



// If this is a prime number then display it
if(blnIsPrime)


// Increment number by two
lngNumber += 2;

// Once the thread is finished execution enable the start
// and disable the pause button
cmdStart.Enabled = true;
cmdPause.Enabled = false;

Well everything is ready now. Let's run the code and see how our application looks.

Well, everything looks good now and the code is apparently working fine. But there is a huge flaw in our code. When the GeneratePrimeNumbers() method finds a prime number it adds the prime number back to the listbox control. It may not look like a problem for you if this code is running in a synchronized execution manner where both the prime number generation code and the user interface are running on the same thread. But in our example, the UI is running in a different thread from the GeneratePrimeNumbers() method. Therefore, when we go between threads to write data this could cause some unexpected behaviors in our application.

The best way to address this problem is using delegates. We can declare a delegate and we can use the delegate to inform the UI thread to update the listbox control itself. In this way, we're not crossing the threading boundaries and the application stability is not compromised.

Let's see how we can implement this operation. Let's add one more public delegate called UpdateData:

public delegate void UpdateData(string returnVal);

Let's modify the GeneratePrimeNumbers() method a little bit to call the delegate from it. We've added a new string array with the initial value as 2, since the first prime number is 2. Then we've declared a new object of the type UpdateData as a delegate and we've passed the address of the UpdateUI method. Then we've used the this. Invoke method with the delegate object and the string array to inform the user interface to update itself. We've done the same when we found a prime number.

Form1 is represented as this in this context.

public void GeneratePrimeNumbers()
;
UpdateData UIDel = new UpdateData(UpdateUI);


this.Invoke(UIDel, args);

while(lngCounter <= 255)


}

}

The UpdateUI() method simply accepts the value that needs to be added to the listbox in its parameter and adds the value to the listbox. Since the UpdateUI method runs in the UI thread there are no cross-boundary thread updates and the stability of our application is not compromised:

void UpdateUI(string result )


Destroying Threads

The Abort() method can be used to destroy the current thread. The Abort() method would be very useful, if you want to terminate the thread for whatever reason, such as the thread is taking too much time to execute or the user has changed their mind by selecting cancel. You might want this behavior in a search process that takes a long time. A search may continue running but the user may have seen the results they wish to see and discontinue the thread that is carrying on the search routine. When Abort() is called against a thread, the ThreadAbortException exception will be raised. If it isn't caught in any code in the thread, then the thread will terminate. Think twice before writing generic exception handling code inside methods that will be accessed in a multithreaded context, since a catch (Exception e) will also catch ThreadAbortExceptions - from which you probably don't want to recover. As we'll see, ThreadAbortException isn't so easily stopped, and your program flow may not continue as you expect it to.

Let's see an example for this. We're going to create a new project called Destroying and we'll copy the code from the previous prime number generation code into the new Form1.cs class. Let's add one more Stop button to the UI like this.

Let's add the following code into the Stop button.

private void cmdStop Click(object sender, System.EventArgs e)

This example is very similar to the previous example. The only difference is that we're using the Abort() method to destroy the thread when the user clicks on the Stop button. Then we're enabling the Start button and disabling all other buttons. You might also note that the ThreadAbortException is a special exception. Like all other exceptions it can be caught. However, once the catch block has completed, the exception will automatically be raised again. When the exception is raised, the runtime executes all the finally blocks before killing the thread.

Joining Threads

The Join() method blocks a given thread until the currently running thread is terminated. When we call the Join() method against a given thread instance, the thread will be placed in the WaitSleepJoin state. This method is very useful, if one thread is dependent upon another thread. By simply joining two threads we are saying that the thread that is running when the Join() method is called will enter the WaitSleepJoin state and not return to the Running state until the thread upon which the Join() method was called completes its tasks. This may sound a bit confusing, but let's see an example for this in the following code sample, thread_joining.cs:

using System;
using System.Threading;
namespace Chapter_02


static void Second()


public static void Main()



In this simple example, the aim is to output numbers to the console sequentially, starting at 1 and finishing at 500. The First() method will output the first 250 numbers and the Second() method will produce those from 251 to 500. Without the FirstThread.Join() line in the Second() method, execution would switch back and forth between the two methods and our output would be scrambled (try commenting out the line and running the example again). By calling the FirstThread.Join() method within the Second() method, the execution of the Second() method is paused until the execution of whatever is in FirstThread (the First() method) has completed.

The Join() method is overloaded; it can accept either an integer or a TimeSpan as a single parameter and returns a Boolean. The effect of calling one of the overloaded versions of this method is that the thread will be blocked until either the other thread completes or the time period elapses, whichever occurs first. The return value will be true if the thread has completed and false if it has not.

Why Not Thread Everything?

We've seen several very useful benefits to threading; we can have several processes running at once, and several threads running within those processes. So, with all these benefits, why don't we just use new threads for all of our methods? Wouldn't that just make everything run fast? Not really. As a matter of fact, we will see in this section that quite the opposite can happen if we overuse threading.

Multithreaded applications require resources. Threads require memory to store the thread-local storage container. As you can imagine, the number of threads used is limited by the amount of memory available. Memory is fairly inexpensive these days so many computers have large amounts of memory. However, you should not assume that this is the case. If you are running your application on an unknown hardware configuration, you cannot assume that your application will have enough memory. Additionally, you cannot assume that your process will be the only one spawning threads and consuming system resources. Just because a machine has a lot of memory, doesn't mean it's all for your application.

You will also discover that each thread also incurs additional processor overhead. Creating too many threads in your applications will limit the amount of time that your thread has to execute. Therefore, your processor could potentially spend more time switching between threads as opposed to actually executing the instructions that the threads contain. If your application is creating more threads, your application will gain more execution time than all the other processes with fewer threads.

To make this concept easier to understand, take the parallel example you'll find down at your local grocery store. Two cashiers are scanning groceries for their customers. However, there is only one bagger, who takes turns switching between the two cashiers. The bagger is rather efficient at jumping back and forth between the two registers and bagging the groceries because they don't pile up any faster than the bagger can bag the groceries. However, if two more cashiers open up lanes, it will become apparent that the bagger will spend more time jumping back and forth between the registers than they spend actually bagging groceries. Eventually, the store will need to get another bagger. In the case of threading, think of the cashiers as applications - or threads, and the bagger as a processor. The processor has to switch between threads. As the 'threads' increase, the grocery store has to add another 'processor' to be sure that the customers get the attention they need.

The phrase 'too many threads' is a rather generic term - and rightly so. What constitutes 'too many' on one system could be fine on another. Since hardware configurations largely dictate the number of threads available on a system, 'too many' is an unquantifiable variable without specific configuration details and lots of testing.

It is for these reasons that Microsoft recommends that you use as few threads as possible in your applications. This limits the amount of resources required by the operating system.

Threading Opportunities

So, now I may have you wondering why we would thread at all if it could potentially have a negative impact on our application. The idea is that you will learn there is a time and place for threading. Learning which circumstances represent good opportunities to spawn a new thread is the key to making good design decisions. There are two distinct opportunities for which to consider spawning a new thread. In this section, we will discuss what those opportunities are.

Background Processes

The first opportunity to spawn a new thread occurs when your application needs to run a large process in the background while still keeping its user interface active and usable. We have all run into times when an application just didn't seem to respond because we had told it to query data or process a large piece of information. Take, for example, the case of professional graphics packages that are required to render graphics into a specific file format. In early versions of some products, asking the application to render a large graphic would result in the application becoming unresponsive until the rendering process had finished. You'd often have to finish working with the application, then set it to render overnight - coming back in the morning to see if the results were what you expected - because sitting and waiting in front of the computer for an hour was just not viable. This problem presents an ideal time to set a background thread to do your computer-intensive processing while leaving your user interface to run on the main application thread.

Let's take a look at an example of a background process that needs to spawn a new thread. This example demonstrates searching for files. When the search routine finds a file matching the pattern specified, it adds a new item to the ListBox.

The code below will demonstrate that this method does indeed need its own thread:

using System.Threading;
using System.IO;
public class Threaded Search : System.Windows.Forms.Form


public void Search()


public void SearchDirectory(string Path)


Go ahead and compile this example and run it. Type a search term in the search textbox, such as * . *, click the Single Thread Search button, and observe our problem. As you will see, we are searching for files and trying to update the user interface every time we find a file with our search criteria. However, because both the user interface and the search code are running on the same thread, we don't see the updates until the search code has completely finished its processing. Additionally, we cannot resize our form while the search is processing.

This rather long piece of code is actually a very simple demonstration. Let's see if we can correct this problem with a simple change. In the Button2_Click routine, add the following code to call the Search() method with the use of a new thread:

private void cmdMulti Click(object sender, System.EventArgs e)


Now recompile and run the program again. This time, type in the same search term and click the Multi Thread Search button. You can see that there is quite a difference. This time our results are displayed immediately. This is because Windows is now switching execution back and forth between the user interface and the search thread. The processor is now given a time slice to update the interface to reflect the changes in the ListBox. You will also notice that we can now resize our form while it is searching.

There are other background processes that may cause our interface to be unresponsive. We might want to do some intense processing, such as searching, sorting, formatting, parsing, and filtering a large number of records in a database. This would be another opportunity to consider using a new thread. You may also want to spawn a new thread if you want to run a routine that is constantly logging information. The user interface won't necessarily be unresponsive in this instance, but it may appear slow or sluggish if this type of routine isn't on its own thread.

Accessing External Resources

The second circumstance in which you might want to consider spawning a new thread occurs when you are accessing resources that are not local to your system. This might be a database process or a network file share somewhere on your network. In such cases, network performance could adversely affect your application performance.

Let's take the following example. We are going to connect to a database in this example. Let's assume that network performance is poor and may cause this application to be slow. Let's also assume that company policy dictates that no applications can be installed on the database server:

using System.Threading;
using System.IO;
using System.Data;
using System.Data.SqlClient;

namespace Chapter 02


public void QueryData()

objReader.Close();
objConn.Close();


public static void Main()



As you can see in this example, all we are doing is querying a remote database. The data returned will not be excessive, but you will notice that the user interface freezes while it takes time to get the data and update the listbox. We can again correct this by simply spawning a new thread and executing our database code within that thread. Let's add a second button and use the following code:

private void button2 Click(object sender, System.EventArgs e)

Now when we run the code, we get a result similar to our last example. We can resize the form while the query runs. The interface is responsive throughout the entire query process.

Of course, I want to reiterate that this doesn't necessarily mean you should spawn a new thread every time you connect to a database. However, analyze your options to find out if you can move the database or the application so they reside on the same server. Also, make sure that this component isn't going to be continuously called from various client applications. Doing so would spawn additional threads for every call and consume more resources than you intended. There are ways to reuse objects and their threads without using a new thread every time your object is called. These issues will be covered in Chapters 3 and 5.



Politica de confidentialitate | Termeni si conditii de utilizare



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 1320
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved