Tải bản đầy đủ - 0 (trang)
4-6. Execute a Method Using a New Thread

4-6. Execute a Method Using a New Thread

Tải bản đầy đủ - 0trang

CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION



the mechanism through which you create and control threads. To create and start a new thread, follow

this process:

1.



Define a method that matches the ThreadStart or ParameterizedThreadStart

delegate. The ThreadStart delegate takes no arguments and returns

void. This means you cannot easily pass data to your new thread. The

ParameterizedThreadStart delegate also returns void but takes a single object as

an argument, allowing you to pass data to the method you want to run. (The

ParameterizedThreadStart delegate is a welcome addition to .NET 2.0.) The

method you want to execute can be static or an instance method.



2.



Create a new Thread object and pass your method as an argument to the

Thread constructor. The new thread has an initial state of Unstarted (a member

of the System.Threading.ThreadState enumeration) and is a foreground thread

by default. If you want to configure it to be a background thread, you need to

set its IsBackground property to true.



3.



Call Start on the Thread object, which changes its state to

ThreadState.Running and begins execution of your method. If you need to pass

data to your method, include it as an argument to the Start call. If you call

Start more than once, it will throw a System.Threading.ThreadStateException.



The Code

The following code demonstrates how to execute a method in a new thread and shows you how to pass

data to the new thread:

using System;

using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter04

{

class Recipe04_06

{

// A utility method for displaying useful trace information to the

// console along with details of the current thread.

private static void TraceMsg(string msg)

{

Console.WriteLine("[{0,3}] - {1} : {2}",

Thread.CurrentThread.ManagedThreadId,

DateTime.Now.ToString("HH:mm:ss.ffff"), msg);

}

// A private class used to pass initialization data to a new thread.

private class ThreadStartData

{

public ThreadStartData(int iterations, string message, int delay)

{

this.iterations = iterations;



170



www.it-ebooks.info



CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION



this.message = message;

this.delay = delay;

}

// Member variables hold initialization data for a new thread.

private readonly int iterations;

private readonly string message;

private readonly int delay;

// Properties provide read-only access to initialization data.

public int Iterations { get { return iterations; } }

public string Message { get { return message; } }

public int Delay { get { return delay; } }

}

// Declare the method that will be executed in its own thread. The

// method displays a message to the console a specified number of

// times, sleeping between each message for a specified duration.

private static void DisplayMessage(object config)

{

ThreadStartData data = config as ThreadStartData;

if (data != null)

{

for (int count = 0; count < data.Iterations; count++)

{

TraceMsg(data.Message);

// Sleep for the specified period.

Thread.Sleep(data.Delay);

}

}

else

{

TraceMsg("Invalid thread configuration.");

}

}

public static void Main()

{

// Create a new Thread object specifying DisplayMessage

// as the method it will execute.

Thread thread = new Thread(DisplayMessage);

// Make this a foreground thread - this is the

// default - call used for example purposes.

thread.IsBackground = false;

// Create a new ThreadStartData object to configure the thread.

ThreadStartData config =

new ThreadStartData(5, "A thread example.", 500);

TraceMsg("Starting new thread.");



171



www.it-ebooks.info



CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION



// Start the new thread and pass the ThreadStartData object

// containing the initialization data.

thread.Start(config);

// Continue with other processing.

for (int count = 0; count < 13; count++)

{

TraceMsg("Main thread continuing processing...");

Thread.Sleep(200);

}

// Wait to continue.

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Main method complete. Press Enter.");

Console.ReadLine();

}

}

}



4-7. Synchronize the Execution of Multiple Threads Using a

Monitor

Problem

You need to coordinate the activities of multiple threads within a single process to ensure the efficient

use of shared resources or to ensure that several threads are not updating the same shared resource at

the same time. (See recipe 4-9 for details of coordination between processes.)



Solution

Identify an appropriate object to use as a mechanism to control access to the shared resource/data. Use

the static method Monitor.Enter to acquire a lock on the object, and use the static method Monitor.Exit

to release the lock so another thread may acquire it.



How It Works

The greatest challenge in writing a multithreaded application is ensuring that the threads work in

concert. This is commonly referred to as thread synchronization, and includes the following:





Ensuring that threads access shared objects and data correctly so that they do not

cause corruption







Ensuring that threads execute only when they are meant to and cause minimum

overhead when they are idle



172



www.it-ebooks.info



CHAPTER 4 ■ THREADS, PROCESSES, AND SYNCHRONIZATION



The most commonly used synchronization mechanism is the System.Threading.Monitor class. The

Monitor class allows a single thread to obtain an exclusive lock on an object by calling the static method

Monitor.Enter. By acquiring an exclusive lock prior to accessing a shared resource or shared data, you

ensure that only one thread can access the resource concurrently. Once the thread has finished with the

resource, release the lock to allow another thread to access it. A block of code that enforces this behavior

is often referred to as a critical section.



■ Note Monitors are managed-code synchronization mechanisms that do not rely on any specific operating

system primitives. This ensures that your code is portable should you want to run it on a non-Windows platform.

This is in contrast to the synchronization mechanisms discussed in recipes 4-8, 4-9, and 4-10, which rely on

Win32 operating system–based synchronization objects.



You can use any object to act as the lock; it is common to use the keyword this to obtain a lock on

the current object, but it is better to use a separate object dedicated to the purpose of synchronization.

The key point is that all threads attempting to access a shared resource must try to acquire the same lock.

Other threads that attempt to acquire a lock using Monitor.Enter on the same object will block (enter a

WaitSleepJoin state), and will be added to the lock’s ready queue until the thread that owns the lock

releases it by calling the static method Monitor.Exit. When the owning thread calls Exit, one of the

threads from the ready queue acquires the lock. If the owner of a lock does not release it by calling Exit,

all other threads will block indefinitely. Therefore, it is important to place the Exit call within a finally

block to ensure that it is called even if an exception occurs. To ensure that threads do not wait

indefinitely, you can specify a timeout value when you call Monitor.Enter.



■ Tip Because Monitor is used so frequently in multithreaded applications, C# provides language-level support

through the lock statement, which the compiler translates to the use of the Monitor class. A block of code

encapsulated in a lock statement is equivalent to calling Monitor.Enter when entering the block and

Monitor.Exit when exiting the block. In addition, the compiler automatically places the Monitor.Exit call in a

finally block to ensure that the lock is released if an exception is thrown.



Using Monitor.Enter and Monitor.Exit is often all you will need to correctly synchronize access to a

shared resource in a multithreaded application. However, when you are trying to coordinate the

activation of a pool of threads to handle work items from a shared queue, Monitor.Enter and

Monitor.Exit will not be sufficient. In this situation, you want a potentially large number of threads to

wait efficiently until a work item becomes available without putting unnecessary load on the central

processing unit (CPU). This is where you need the fine-grained synchronization control provided by the

Monitor.Wait, Monitor.Pulse, and Monitor.PulseAll methods.

The thread that currently owns the lock can call Monitor.Wait, which will release the lock and place

the calling thread on the lock’s wait queue. Threads in a wait queue also have a state of WaitSleepJoin,

and will continue to block until a thread that owns the lock calls either the Monitor.Pulse method or the

Monitor.PulseAll method. Monitor.Pulse moves one of the waiting threads from the wait queue to the



173



www.it-ebooks.info



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

4-6. Execute a Method Using a New Thread

Tải bản đầy đủ ngay(0 tr)

×