Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C# ПІДРУЧНИКИ / c# / Hungry Minds - C# Bible

.pdf
Скачиваний:
226
Добавлен:
12.02.2016
Размер:
4.21 Mб
Скачать

all. The more memory you have, the more physical address space there is the running applications to create more threads. When you write applications that create threads, be sure you take this into consideration. The more threads you create, the more resources your application consumes. This could actually cause poorer performance than a single-threaded application, depending on the OS. The more the merrier does not include threads. Therefore, use caution when creating threads in that new version of multithreaded Tetris you are writing in C#.

Understanding application domains

Earlier, you learned that the MFC SDK defines a process as an executing instance of an application. Each application that is executing creates a new main thread, which lasts the lifetime of that application instance. Because each application is a process, each instance of an application must have process isolation. Two separate instances of Microsoft Word act independently of each other. When you click Spell Check, InstanceA of Word does not spellcheck the document running in InstanceB of Word. Even if InstanceA of Word attempts to pass a memory pointer to InstanceB of Word, InstanceB would not know what to do with it, or even know where to look for it, as memory pointers are only relative to the process in which they are running.

In the .NET Framework, application domains are used to provide security and application isolation for managed code. Several application domains can run on a single process, or thread, with the same protection that would exist if the applications were running on multiple processes. Overhead is reduced with this concept, as calls do not need to be marshaled across process boundaries if the applications need to share data. Conversely, a single application domain can run across multiple threads.

This is possible because of the way the CLR executes code. Once code is ready to execute, it has already gone through the process of verification by the JIT compiler. By passing this verification process, the code is guaranteed not to do invalid things, such as access memory it is not supposed to, causing a page fault. This concept of type-safe code ensures that your code does not violate any rules after the verifier has approved it passing from MSIL to PE code. In typical Win32 applications, there were no safeguards against one piece of code supplanting another piece of code, so each application needed process isolation. In .NET, because type safety is guaranteed, it is safe to run multiple applications from multiple providers within the same application domain.

Understanding the benefits of multithreaded applications

Several types of applications can take advantage of multithreading.

Applications with long processes

Polling and listener applications

Applications with a Cancel button in the GUI

The following sections state the case for each of these reasons.

Applications with long processes

Applications that involve long processes with which the user does not need to interact can benefit from multithreading because the long-running process can be created on a worker thread that processes information in the background until a notification that the thread has completed is made to the process that called the thread. In the meantime, the user is not kept waiting, staring at an hourglass cursor, to move on to the next task.

Polling and listener applications

Polling applications and listener applications can benefit from multithreading. Suppose you have an application that has created threads that are listening or polling. When something happens, a thread can consume that particular event, and the other threads can continue to poll or listen for events to occur. An example of this is a service that listens for requests on a network port, or a polling application that checks the state of Microsoft Message Queue (MSMQ) for messages. An example of an off-the-shelf polling applications is Microsoft Biztalk Server. Biztalk is constantly polling for things like files in a directory, or files on an SMTP server. It cannot accomplish all of this on a single thread, so multiple threads poll different resources. Microsoft Message Queue has an add-on for Windows 2000 and a feature in Windows XP called Message Queue Triggers. With MSMQ Triggers, you can set properties that cause a trigger to fire an event. This is a multithreaded service that can handle thousands of simultaneous requests.

Cancel buttons

Any application that has a Cancel button on a form should follow this process:

1.Load and show the form modally.

2.Start the process that is occurring on a new thread.

3.Wait for the thread to complete.

4.Unload the form.

By following these steps, the click event of your Cancel button occurs if the user clicks the button while another thread is executing. If the user does click the Cancel button, it actually clicks, as the process is running on a thread other than the currently running thread handling the click event, your code should then stop the process on the other running thread. This is a GUI feature that turns a good application into a great application.

Creating Multithreaded Applications

Now it's time to begin creating multithreaded applications. Threading is handled through the System.Threading namespace. The common members of the Thread class that you use are listed in Table 33-1.

 

Table 33-1: Common Thread Class Members

 

 

 

 

Member

 

 

Description

 

 

 

 

CurrentContext

 

 

Returns the current context on which the thread is executing

 

 

 

 

CurrentThread

 

 

Returns a reference to the currently running thread

 

 

 

 

ResetAbort

 

 

Resets an abort request

 

 

 

 

Sleep

 

 

Suspends the current thread for a specified length of time

 

 

 

 

 

Table 33-1: Common Thread Class Members

 

 

 

 

Member

 

 

Description

 

 

 

 

ApartmentState

 

 

Gets or sets the apartment state of the thread

 

 

 

 

IsAlive

 

 

Gets a value that indicates whether the thread has been started and

 

 

 

is not dead

 

 

 

 

IsBackground

 

 

Gets or sets a value indicating whether the thread is a background

 

 

 

thread

 

 

 

 

Name

 

 

Gets or sets the name of the thread

 

 

 

 

Priority

 

 

Gets or sets the thread priority

 

 

 

 

Threadstate

 

 

Gets the state of the thread

 

 

 

 

Abort

 

 

Raises the ThreadAbortException, which can end the thread

 

 

 

 

Interrupt

 

 

Interrupts a thread that is in the WaitSleepJoin thread state

 

 

 

 

Join

 

 

Waits for a thread

 

 

 

 

Resume

 

 

Resumes a thread that has been suspended

 

 

 

 

Start

 

 

Begins the thread execution

 

 

 

 

Suspend

 

 

Suspends the thread

 

 

 

 

Creating new threads

Creating a variable of the System.Threading.Thread type enables you to create a new thread to start working with. Because the concept of threading involves the independent execution of another task, the Thread constructor requires the address of a procedure that will do the work for the thread you are creating. The ThreadStart delegate is the only parameter the constructor needs to begin using the thread.

To test this code, create a new project with the Console application template. The code in Listing 33-1 creates two new threads and calls the Start method of the Thread class to get the thread running.

Listing 33-1: Creating New Threads

using System;

using System.Threading;

public class Threads

{

public void Threader1()

{

}

public void Threader2()

{

}

}

public class ThreadTest

{

public static int Main(String[] args)

{

Threads testThreading = new Threads();

Thread t1 = new

Thread(new ThreadStart(testThreading.Threader1)); t1.Start();

Thread t2 = new

Thread(new ThreadStart(testThreading.Threader2)); t2.Start();

Console.ReadLine(); return 0;

}

}

When you create a variable of type thread, the procedure that handles the thread must exist for the ThreadStart delegate. If it does not, an error occurs and your application does not compile.

The Name property sets or retrieves the name of a thread. This enables you to use a meaningful name instead of an address or hash code to reference the running threads. This is useful when using the debugging features of Visual Studio .NET. In the debugging toolbar, a drop-down list of the names of the running threads is available. Although you cannot "step out" of a thread and jump into another thread with the debugger, it is useful to know on which thread an error may have occurred.

Now that the thread variables are declared, named, and started, you need to do something on the threads you have created. The procedure names that were passed to the thread constructor were called Threader1 and Threader2. You can now add some code to these methods to see how they act. Your code should now look something like Listing 33-2.

Listing 33-2: Retreiving Information on Runnnig Threads

using System;

using System.Threading;

public class Threads

{

public void Threader1()

{

Console.WriteLine (" *** Threader1 Information ***");

Console.WriteLine

("Name: " + Thread.CurrentThread.Name); Console.WriteLine

(Thread.CurrentThread);

Console.WriteLine

("State: " + Thread.CurrentThread.ThreadState); Console.WriteLine

("Priority: " + Thread.CurrentThread.Priority); Console.WriteLine(" *** End Threader1 Information ***");

}

public void Threader2()

{

Console.WriteLine (" *** Threader2 Information ***");

Console.WriteLine

("Name: " + Thread.CurrentThread.Name); Console.WriteLine

(Thread.CurrentThread);

Console.WriteLine

("State: " + Thread.CurrentThread.ThreadState); Console.WriteLine

("Priority: " + Thread.CurrentThread.Priority); Console.WriteLine(" *** End Threader2 Information ***");

}

}

public class ThreadTest

{

public static int Main(String[] args)

{

Threads testThreading = new Threads();

Thread t1 = new

Thread(new ThreadStart(testThreading.Threader1)); t1.Name = "Threader1";

t1.Start();

Thread t2 = new

Thread(new ThreadStart(testThreading.Threader2)); t2.Name = "Threader2";

t2.Start();

Console.ReadLine(); return 0;

}

}

When you run the application, your console output should look something like that shown in Figure 33-2.

Figure 33-2: Threading application output

The output displayed in Figure 33-2 is not very pretty. If you recall, you are working with threads. Without setting a property or two, your Threader1 procedure never completes before Threader2 starts.

When the following code executes

t1.Start();

it begins the execution of the Threader1 code. Because it is a thread, it has roughly 20 milliseconds of the time slice. In that time period, it reached the second line of code in the function, passed control back to the operating system, and executed the following line of code:

t2.start();

The Threader2 procedure then executes for its slice of time and is preempted by the t1 thread. This back-and-forth process continues until both procedures can finish.

Understanding thread priority

For the Threader1 procedure to finish before the Threader2 procedure begins, you need to set the Priority property to the correct ThreadPriority enumeration to ensure that the t1 thread has priority over any other thread. Before the t1.Start method call, add the following code:

t1.Priority = ThreadPriority.Highest;

When you set the priority to highest, t1 finishes before t2. If you run the application again, your output should look similar to that shown in Figure 33-3.

Figure 33-3: Output after setting the thread priority

The ThreadPriority enumeration dictates how a given thread is scheduled based on other running threads. ThreadPriority can be any one of the following: AboveNormal, BelowNormal, Highest, Lowest, or Normal. The algorithm that determines thread scheduling varies depending on the operating system on which the threads are running. By default, when a new thread is created, it is given a priority of 2, which is Normal in the enumeration.

Understanding thread state

When you create a new thread, you call the Start() method. At this point, the operating system allocates time slices to the address of the procedure passed in the thread constructor. Though the thread might live for a very long time, it still passes in between different states while other

threads are being processed by the operating system. This state might be useful to you in your application. Based on the state of a thread, you could determine that something else might need to be processed. Besides Start, the most common thread states you will use are Sleep and Abort. By passing a number of milliseconds to the Sleep constructor, you are instructing the thread to give up the remainder of its time slice. Calling the Abort method stops the execution of the thread. Listing 33-3 shows some code that uses both Sleep and Abort.

Listing 33-3: Using the Thread.Sleep Method

using System;

using System.Threading;

public class Threads

{

public void Threader1()

{

for(int intX = 0; intX < 50;intX ++)

{

if(intX == 5){ Thread.Sleep(500);

Console.WriteLine("Thread1 Sleeping");}

}

}

public void Threader2()

{

for(int intX = 0; intX < 50;intX ++)

{

if(intX == 5){ Thread.Sleep(500);

Console.WriteLine("Thread2 Sleeping");}

}

}

}

public class ThreadTest

{

public static int Main(String[] args)

{

Threads testThreading = new Threads();

Thread t1 = new

Thread(new ThreadStart(testThreading.Threader1)); t1.Priority = ThreadPriority.Highest;

t1.Start();

Thread t2 = new

Thread(new ThreadStart(testThreading.Threader2)); t2.Start();

Console.ReadLine(); return 0;

}

}

If you notice, the Priority property is set to highest for the t1 thread. This means that no matter what, it executes before t2 starts. However, in the Threader1 procedure, you have the following if block:

for(int intX = 0; intX < 50;intX ++)

{

if(intX == 5){ Thread.Sleep(500);

Console.WriteLine("Thread2 Sleeping");}

}

This tells the t1 thread to sleep for 500 milliseconds, giving up its current time slice and allowing the t2 thread to begin. After both threads are complete, the Abort method is called, and the threads are killed.

The Thread.Suspend method calls suspend a thread, indefinitely, until another thread wakes it back up. If you ever noticed the processor meter in the task manager spike at 100 percent when you aren't losing any memory, you can understand what happens when a thread is suspended. To get the thread back on track, you need to call the Resume method from another thread so it can restart itself. The following code demonstrates Suspend and Resume methods:

Thread.CurrentThread.Suspend;

Console.WriteLine("Thread1 Suspended");

Thread.CurrentThread.Resume;

Console.WriteLine("Thread1 Resumed");

A big caution is in order here: Suspending threads can cause undesirable results. You must ensure that the thread is resumed by another thread. Figure 33-4 demonstrates the issues described in the preceding paragraph. Notice in the figure that the console window is at the T1 Suspended line of code. This example reflects a test case, so you can get rid of the Resume method. The task manager results speak for the state of the system.

Figure 33-4: Spiked processor from a suspended thread

ThreadState is a bitwise combination of the FlagsAttribute enumeration. At any given time, a thread can be in more than one state. For example, if a thread is a background thread, and it is

currently running, then the state would be both Running and Background. Table 33-2 describes the possible states a thread can be in.

 

Table 33-2: ThreadState Members

 

 

 

Member

 

Description

 

 

 

Aborted

 

The thread has aborted.

 

 

 

AbortRequested

 

A request has been made to abort a thread.

 

 

 

Background

 

The thread is executing as a backgroung thread.

 

 

 

Running

 

The thread is being executed.

 

 

 

Suspended

 

The thread has been suspended.

 

 

 

SuspendRequested

 

The thread is being requested to suspend.

 

 

 

Unstarted

 

The thread has not been started.

 

 

 

WatSleepJoin

 

The thread is blocked on a call to Wait, Sleep, or Join.

 

 

 

Joining threads

 

 

The Thread.Join method waits for a thread to finish before continuing processing. This is useful if you create several threads that are supposed to accomplish a certain task, but before you want the foreground application to continue, you need to ensure that all of the threads you created were completed. In the following code, switch

T2.Join();

with

Console.Writeline("Writing");

You get two sets of results, the second time you run the code. The console output of Writing does not show up until both threads have finished.

Listing 33-4: Joining Threads

using System;

using System.Threading;

public class Threads

{

public void Threader1()

{

for(int intX = 0; intX < 50;intX ++)

{

if(intX == 5)

{

Thread.Sleep(500); Console.WriteLine("Thread1 Sleeping");

}

}

}

public void Threader2()

{

for(int intX = 0; intX < 50;intX ++)

{

if(intX == 5)

{

Thread.Sleep(500); Console.WriteLine("Thread2 Sleeping");

}

}

}

}

public class ThreadTest

{

public static int Main(String[] args)

{

Threads testThreading = new Threads();

Thread t2 = new

Thread(new ThreadStart(testThreading.Threader2)); t2.Start();

Thread t1 = new

Thread(new ThreadStart(testThreading.Threader1)); t1.Priority = ThreadPriority.Highest;

t1.Start();

/* Call Join to wait for all threads to complete */

t2.Join();

Console.WriteLine("Writing");

Console.ReadLine(); return 0;

}

}

As you can see, setting various properties on threads makes it very simple to control them. Keep in mind that after you suspend a thread, you need to resume it, or your system consumes unnecessary resources.

Synchronizing threads

Data synchronization is a critical aspect of using threads. Although it is not a complex programming task, your data risks corruption if you fail to address it.

When threads are running, they are sharing time with other running threads. This is evident in the sample you have run in this chapter. If you have a method that is running on multiple threads, each thread has only several milliseconds of processor time before the operating system preempts the thread to give another thread time in the same method. If you are in the middle of a math statement, or in the middle on concatenating a name, your thread could very

Соседние файлы в папке c#