Tải bản đầy đủ (.pdf) (82 trang)

mcts self paced training kit exam 70-536 microsoft net framework 3.5 application development foundation phần 5 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (533.31 KB, 82 trang )

296 Chapter 7 Threading
it increments the value as it was before Thread2 updated it—thus completing the
increment operation but rewriting the value with 21. In this scenario, two incre-
ment operations resulted in incrementing the value only one time. As the previous
code sample demonstrates, this can and does happen in real-world programming
environments, and mathematical errors such as this can be disastrous.
To correct the problem, replace the number += 1 operation in myNum.AddOne with
Interlocked.Increment(ref number), and then run the application again. This time, the
results are perfect because Interlocked.Increment does not allow another thread to
interrupt the increment operation.
IMPORTANT Multithreading Best Practices
For more information about how to minimize problems when writing multithreaded applications,
read “Managed Threading Best Practices” at the MSDN Library ( />library/1c9txz50.aspx).
Waiting for Threads to Complete
Often, your application’s primary thread must wait for background threads to com-
plete before continuing. If you are waiting on a single thread, you can simply call
Thread.Join, which halts processing until the thread terminates.
If you need to wait for multiple threads to complete, use the WaitHandle.WaitAll static
method with an AutoResetEvent array. The following code sample demonstrates this. In
this example, the custom ThreadInfo class provides everything that the background
thread needs to execute; namely, an integer that represents the number of milliseconds
to wait and an AutoResetEvent instance that it can set (by calling AutoResetEvent.Set)
when processing is complete:
' VB
' Define an array with three AutoResetEvent WaitHandles.
Dim waitHandles As AutoResetEvent() = New AutoResetEvent() _
{New AutoResetEvent(False), _
New AutoResetEvent(False), _
New AutoResetEvent(False)}

<MTAThread()> Sub Main()


' Queue up tasks on different threads; wait until all tasks are
' completed.
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoTask), _
New ThreadInfo(3000, waitHandles(0)))
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoTask), _
Lesson 2: Managing Threads 297
New ThreadInfo(2000, waitHandles(1)))
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoTask), _
New ThreadInfo(1000, waitHandles(2)))
WaitHandle.WaitAll(waitHandles)
Console.WriteLine("Main thread is complete.")
Console.ReadKey()
End Sub

Sub DoTask(ByVal state As Object)
Dim ti As ThreadInfo = DirectCast(state, ThreadInfo)
Thread.Sleep(ti.ms)
Console.WriteLine("Waited for " + ti.ms.ToString() + " ms.")
ti.are.Set()
End Sub

Class ThreadInfo
Public are As AutoResetEvent
Public ms As Integer

Public Sub New(ByVal _ms As Integer, ByVal _are As AutoResetEvent)
ms = _ms
are = _are
End Sub
End Class

// C#
// Define an array with three AutoResetEvent WaitHandles.
static AutoResetEvent[] waitHandles = new AutoResetEvent[]
{
new AutoResetEvent(false),
new AutoResetEvent(false),
new AutoResetEvent(false)
};

static void Main()
{
// Queue up tasks on different threads; wait until all tasks are
// completed.
ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask),
new ThreadInfo(3000, waitHandles[0]));
ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask),
new ThreadInfo(2000, waitHandles[1]));
ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask),
new ThreadInfo(1000, waitHandles[2]));
WaitHandle.WaitAll(waitHandles);
Console.WriteLine("Main thread is complete.");
Console.ReadKey();
}

static void DoTask(Object state)
{
ThreadInfo ti = (ThreadInfo)state;
Thread.Sleep(ti.ms);
298 Chapter 7 Threading
Console.WriteLine("Waited for " + ti.ms.ToString() + " ms.");

ti.are.Set();
}

class ThreadInfo
{
public AutoResetEvent are;
public int ms;

public ThreadInfo(int _ms, AutoResetEvent _are)
{
ms = _ms;
are = _are;
}
}
If you run that code, you see the following output:
Waited for 1000 ms.
Waited for 2000 ms.
Waited for 3000 ms.
Main thread is complete.
Notice that the message “Main thread is complete” displays only after all three threads
have completed, indicating that the Main thread waited for the threads before con-
tinuing. If you comment out the call to WaitHandle.WaitAll, you see the following out-
put, which indicates that the Main thread continued to process without waiting for
the background threads:
Main thread is complete.
Waited for 1000 ms.
Waited for 2000 ms.
Waited for 3000 ms.
You can also call WaitHandle.WaitAny, which waits for the first thread to return. In this
example, replacing WaitHandle.WaitAll with WaitHandle.WaitAny produces the fol-

lowing output:
Waited for 1000 ms.
Main thread is complete.
Waited for 2000 ms.
Waited for 3000 ms.
In this example, notice that the Main method in Visual Basic has the MTAThread
attribute. Without it, the Main method would be started as a single-threaded apartment
(STA) thread. STA is designed to be used in single-threaded environments. Only multi-
threaded apartment (MTA) threads support calling WaitHandle.WaitAll. C# starts the
main method as an MTA thread by default, and thus it does not require the MTAThread
attribute (although you could add it if you wanted; it’s just unnecessary because it’s the
default setting).
Lesson 2: Managing Threads 299
Lab: Manage Threads
In this lab, you will expand the application that you created in Lesson 1 so that it
properly waits for threads to complete. Then, you convert it to use multiple Thread
instances rather than calling ThreadPool.QueueUserWorkItem.
Exercise 1: Wait for Threads to Complete In this exercise, you must update the appli-
cation you created in Lesson 1 so that it waits for all threads to complete before dis-
playing the results.
1. Navigate to the \<InstallHome>\Chapter07\Lesson2\Exercise1\Partial folder
and open either the C# version or the Visual Basic .NET version of the solution
file. Alternatively, you can continue working from the project you created for
Lesson 1.
2. Build and run the application. Notice that the program attempts to display the
time elapsed while retrieving the five Web pages, but it displays an incorrect
value because it does not wait for the GetPage threads to complete.
3. First, create an array of AutoResetEvent objects within the Main method, with one
element for every Uniform Resource Locator (URL). The following declaration
works, but it must be placed after the urls variable is declared:

' VB
Dim waitHandles As AutoResetEvent() = New AutoResetEvent( _
urls.Length - 1) {}
// C#
AutoResetEvent[] waitHandles = new AutoResetEvent[urls.Length];
4. To use the waitHandles array, you must pass one element of the array to each
thread. To do that, you must pass a single object to the method being called, and
that object must contain both the element of the waitHandles array and the
string that will be used as the URL. Therefore, you must create a new class that
contains two members: an instance of AutoResetEvent and a string for the URL.
The following demonstrates how to create this class:
' VB
Class ThreadInfo
Public url As String
Public are As AutoResetEvent

Public Sub New(ByVal _url As String, ByVal _are As AutoResetEvent)
url = _url
are = _are
End Sub
End Class
300 Chapter 7 Threading
// C#
class ThreadInfo
{
public string url;
public AutoResetEvent are;

public ThreadInfo(string _url, AutoResetEvent _are)
{

url = _url;
are = _are;
}
}
5. Update the GetPage method to cast the data object to an instance of ThreadInfo,
as shown here:
' VB
Sub GetPage(ByVal data As Object)
' Cast the object to a ThreadInfo
Dim ti As ThreadInfo = DirectCast(data, ThreadInfo)

' Request the URL
Dim wr As WebResponse = WebRequest.Create(ti.url).GetResponse()

' Display the value for the Content-Length header
Console.WriteLine(ti.url + ": " + wr.Headers("Content-Length"))

wr.Close()

' Let the parent thread know the process is done
ti.are.Set()
End Sub
// C#
static void GetPage(object data)
{
// Cast the object to a ThreadInfo
ThreadInfo ti = (ThreadInfo)data;

// Request the URL
WebResponse wr = WebRequest.Create(ti.url).GetResponse();


// Display the value for the Content-Length header
Console.WriteLine(ti.url + ": " + wr.Headers["Content-Length"]);

wr.Close();

// Let the parent thread know the process is done
ti.are.Set();
}
Lesson 2: Managing Threads 301
6. Next, update the foreach loop in the Main method to create an instance of the
ThreadInfo class and pass it to the GetPage method, as demonstrated here:
' VB
Dim i As Integer = 0
For Each url As String In urls
waitHandles(i) = New AutoResetEvent(False)
Dim ti As New ThreadInfo(url, waitHandles(i))
ThreadPool.QueueUserWorkItem(AddressOf GetPage, ti)
i += 1
Next
// C#
int i = 0;
foreach (string url in urls)
{
waitHandles[i] = new AutoResetEvent(false);
ThreadInfo ti = new ThreadInfo(url, waitHandles[i]);
ThreadPool.QueueUserWorkItem(GetPage, ti);
i++;
}
7. Finally, call the static WaitHandle.WaitAll method before displaying the elapsed

time, as demonstrated here:
' VB
WaitHandle.WaitAll(waitHandles)
// C#
WaitHandle.WaitAll(waitHandles);
8. If you are using Visual Basic, add the MTAThread attribute to the Main method.
(This is not required in C#, because it is the default.)
' VB
<MTAThread()> Sub Main()
9. Build and run the application. Notice that it correctly waits until all threads have
returned before displaying the elapsed time. Bug fixed!
Exercise 2: Pass Values Back from Threads In this exercise, you will update the appli-
cation that you created in Lesson 1 to create a Thread object and return results to
a callback method rather than calling ThreadPool.QueueUserWorkItem.
1. Navigate to the \<InstallHome>\Chapter07\Lesson2\Exercise2\Partial folder
and open either the C# version or the Visual Basic .NET version of the solution
file. Alternatively, you can continue working from the project you created for
Lesson 2, Exercise 1.
302 Chapter 7 Threading
2. To pass data to a method when you create an instance of Thread, you need a non-
static method. Therefore, you should replace the GetPage method and ThreadInfo
class with a class containing both parameters and a method that the thread will
run, as shown here:
' VB
Public Class PageSize
Public url As String
Public are As AutoResetEvent
Public bytes As Integer

Public Sub New(ByVal _url As String, ByVal _are As AutoResetEvent)

url = _url
are = _are
End Sub

Public Sub GetPageSize()
' Request the URL
Dim wr As WebResponse = WebRequest.Create(url).GetResponse()

bytes = Integer.Parse(wr.Headers("Content-Length"))

' Display the value for the Content-Length header
Console.WriteLine(url + ": " + bytes.ToString())

wr.Close()

' Let the parent thread know the process is done
are.Set()
End Sub
End Class
// C#
public class PageSize
{
public string url;
public AutoResetEvent are;
public int bytes;

public PageSize(string _url, AutoResetEvent _are)
{
url = _url;
are = _are;

}

public void GetPageSize()
{
// Request the URL
WebResponse wr = WebRequest.Create(url).GetResponse();

bytes = int.Parse(wr.Headers["Content-Length"]);
Lesson 2: Managing Threads 303

// Display the value for the Content-Length header
Console.WriteLine(url + ": " + bytes.ToString());

wr.Close();

// Let the parent thread know the process is done
are.Set();
}
}
3. Threads can return values by calling a callback method that you pass to the
thread as a parameter. To create a callback method, write the method and create
a matching delegate. In this case, we want the thread to return both the URL and
the bytes in the Web page, so we can simply pass an instance of the PageSize class.
For the purpose of this example, the callback method can simply display the out-
put to the console. The following code creates the method and the callback:
' VB
' The callback method must match the signature of the callback delegate.
Sub ResultCallback(ByVal ps As PageSize)
Console.WriteLine("{0}: {1}", ps.url, ps.bytes.ToString())
End Sub


' Delegate that defines the signature for the callback method.
Delegate Sub ResultDelegate(ByVal ps As PageSize)
// C#
// The callback method must match the signature of the callback delegate.
static void ResultCallback(PageSize ps)
{
Console.WriteLine("{0}: {1}", ps.url, ps.bytes.ToString());
}

// Delegate that defines the signature for the callback method.
public delegate void ResultDelegate(PageSize ps);
4. Update the PageSize class to accept the callback method as a parameter in the
constructor, and then store the callback value, as shown here (you should not
delete the GetPageSize method):
' VB
Class PageSize
Public url As String
Public are As AutoResetEvent
Public bytes As Integer

' Delegate used to execute the callback method when the task is
' complete.
Private callback As ResultDelegate

304 Chapter 7 Threading
Public Sub New(ByVal _url As String, ByVal _are As AutoResetEvent, _
ByVal _callback As ResultDelegate)
url = _url
are = _are

callback = _callback
End Sub
End Class
// C#

public class PageSize
{
public string url;
public AutoResetEvent are;
public int bytes;

// Delegate used to execute the callback method when the task is
// complete.
private ResultDelegate callback;

public PageSize(string _url, AutoResetEvent _are,
ResultDelegate _callback)
{
url = _url;
are = _are;
callback = _callback;
}
}
5. Next, update the PageSize class to store the callback method, and accept the call-
back as a parameter for the constructor. In addition, comment out the line in the
GetPageSize method that displays the URL and page size to the console and
instead call the callback method, passing the current PageSize instance. The
following code demonstrates how to update the PageSize class (changes are
shown in bold):
' VB

Public Sub GetPageSize()
' Request the URL
Dim wr As WebResponse = WebRequest.Create(url).GetResponse()

bytes = Integer.Parse(wr.Headers("Content-Length"))

' Display the value for the Content-Length header
''''' Console.WriteLine(url + ": " + bytes.ToString());
wr.Close()
callback(Me)
' Let the parent thread know the process is done
are.[Set]()
End Sub
Lesson 2: Managing Threads 305
// C#
public void GetPageSize()
{
// Request the URL
WebResponse wr = WebRequest.Create(url).GetResponse();

bytes = int.Parse(wr.Headers["Content-Length"]);

// Display the value for the Content-Length header
///// Console.WriteLine(url + ": " + bytes.ToString());

wr.Close();
callback(this);
// Let the parent thread know the process is done
are.Set();
}

6. Finally, update the foreach loop in the Main method to create and start a new
Thread instance rather than calling ThreadPool.QueueUserWorkItem. Pass the call-
back method to the PageSize constructor, as shown here:
' VB
For Each url As String In urls
waitHandles(i) = New AutoResetEvent(False)
Dim ps As New PageSize(url, waitHandles(i), _
New ResultDelegate(AddressOf ResultCallback))
Dim t As New Thread(New ThreadStart(AddressOf ps.GetPageSize))
t.Start()
i += 1
Next
// C#
foreach (string url in urls)
{
waitHandles[i] = new AutoResetEvent(false);
PageSize ps = new PageSize(url, waitHandles[i],
new ResultDelegate(ResultCallback));
Thread t = new Thread(new ThreadStart(ps.GetPageSize));
t.Start();
i++;
}
7. Build and run the application. Although it functions exactly the same as it did in
Lesson 2, Exercise 1, the results are now being processed by a callback method.
In the real world, this is a much more useful scenario—background threads
almost always need to return results to the foreground thread.
306 Chapter 7 Threading
Lesson Summary
Q You can create an instance of the Thread class to provide more control over back-
ground threads than is available using ThreadPool.QueueUserWorkItem. Define

the Thread.Priority property if you want to run the thread using a priority other
than Normal. When the thread is configured, call Thread.Start to begin process-
ing. If you need to stop the background thread, call Thread.Abort. If you might
abort a thread, you should catch ThreadAbortException in the method to allow
the method to close any open resources.
Q If your foreground thread needs to monitor background threads, you can check
the Thread.ThreadState property.
Q When using the Thread class, the easiest way to pass data to a method is to create
an instance of the class containing the method, and define attributes of the class.
To pass data from a thread, define a callback method.
Q Often, multiple threads need access to the same resources. To minimize resource
conflicts, use Monitor locks to allow only a single thread to access a resource. If
you want to provide separate logic for read locks and write locks, create an
instance of the ReaderWriterLock class. To prevent basic mathematical calcula-
tions from being corrupted in multithreaded environments, use the static meth-
ods of the Interlocked class.
Q The simplest way to wait for a thread to complete is to call Thread.Join. To wait for
multiple threads to complete, create an array of AutoResetEvent objects, pass one
item to each thread, and then call WaitHandle.WaitAll or WaitHandle.WaitAny
from the foreground thread.
Lesson Review
You can use the following questions to test your knowledge of the information in
Lesson 2, “Managing Threads.” The questions are also available on the companion
CD if you prefer to review them in electronic form.
NOTE Answers
Answers to these questions and explanations of why each answer choice is right or wrong are
located in the “Answers” section at the end of the book.
1. You will create an application that starts a new Thread object to run a method. You
want the Thread to run as quickly as possible, even if that means it receives more
processor time than the foreground thread. Which code sample does this correctly?

Lesson 2: Managing Threads 307
A.
' VB
Dim DoWorkThread As New Thread(New ThreadStart(AddressOf DoWork))
DoWorkThread.ThreadState = ThreadState.Running
DoWorkThread.Start()
// C#
Thread DoWorkThread = new Thread(new ThreadStart(DoWork));
DoWorkThread.ThreadState = ThreadState.Running;
DoWorkThread.Start();
B.
' VB
Dim DoWorkThread As New Thread(New ThreadStart(AddressOf DoWork))
DoWorkThread.Priority = ThreadPriority.Highest
DoWorkThread.Start()
// C#
Thread DoWorkThread = new Thread(new ThreadStart(DoWork));
DoWorkThread.Priority = ThreadPriority.Highest;
DoWorkThread.Start();
C.
' VB
Dim DoWorkThread As New Thread(New ThreadStart(AddressOf DoWork))
DoWorkThread.Priority = ThreadPriority.Lowest
DoWorkThread.Start()
// C#
Thread DoWorkThread = new Thread(new ThreadStart(DoWork));
DoWorkThread.Priority = ThreadPriority.Lowest;
DoWorkThread.Start();
D.
' VB

Dim DoWorkThread As New Thread(New ThreadStart(AddressOf DoWork))
DoWorkThread.ThreadState = ThreadState.WaitSleepJoin
DoWorkThread.Start()
// C#
Thread DoWorkThread = new Thread(new ThreadStart(DoWork));
DoWorkThread.ThreadState = ThreadState.WaitSleepJoin;
DoWorkThread.Start();
2. You are creating a method that is part of a custom class. The method might be
run simultaneously within multiple threads. You need to ensure that no thread
writes to the file while any thread is reading from the file. You want to provide
the highest level of efficiency when multiple threads are reading from the file
simultaneously. Which code sample should you use?
308 Chapter 7 Threading
A.
' VB
SyncLock file
' Read file
End SyncLock
// C#
lock (file)
{
// Read file
}
B.
SyncLock
' Read file
End SyncLock
// C#
lock
{

// Read file
}
C.
' VB
Dim rwl As New ReaderWriterLock()
rwl.AcquireReaderLock()
' Read file
rwl.ReleaseReaderLock()
// C#
ReaderWriterLock rwl = new ReaderWriterLock();
rwl.AcquireReaderLock();
// Read file
rwl.ReleaseReaderLock();
D.
' VB
Dim rwl As New ReaderWriterLock()
rwl.AcquireReaderLock(10000)
' Read file
rwl.ReleaseReaderLock()
// C#
ReaderWriterLock rwl = new ReaderWriterLock();
rwl.AcquireReaderLock(10000);
// Read file
rwl.ReleaseReaderLock();
Lesson 2: Managing Threads 309
3. You are writing a method that tracks the total number of orders in shopping
carts on your Web site. Orders might come from different users, and the request
to increment the counter might come from different threads. Which of the fol-
lowing code samples increments the orders integer and guarantees accurate
results?

A.
' VB
orders += 1
// C#
orders += 1;
B.
SyncLock orders
orders += 1
End SyncLock
// C#
lock (orders)
{
orders += 1;
}
C.
' VB
Interlocked.Increment(orders)

// C#
Interlocked.Increment(ref orders);
D.
' VB
Dim rwl As New ReaderWriterLock()
rwl.AcquireReaderLock(10000)
orders += 1
rwl.ReleaseReaderLock()
// C#
ReaderWriterLock rwl = new ReaderWriterLock();
rwl.AcquireReaderLock(10000);
orders += 1;

rwl.ReleaseReaderLock();
310 Chapter 7 Review
Chapter Review
To practice and reinforce the skills you learned in this chapter further, you can do any
of the following:
Q Review the chapter summary.
Q Review the list of key terms introduced in this chapter.
Q Complete the case scenarios. These scenarios set up real-world situations involv-
ing the topics of this chapter and ask you to create a solution.
Q Complete the suggested practices.
Q Take a practice test.
Chapter Summary
Q The System.Threading namespace provides classes for starting and managing
multiple threads. The simplest way to start a background thread is to call the
ThreadPool.QueueUserWorkItem method. By providing the address of a method,
the method you specify runs in the background until completion.
Q If you need more control over threads than ThreadPool.QueueUserWorkItem pro-
vides, you can create an instance of the Thread class. The Thread class allows you
to configure the priority of a thread and manually start, suspend, resume, and
abort a thread. Regardless of whether you call ThreadPool.QueueUserWorkItem or
create an instance of the Thread class, you can pass data to and from the thread.
If multiple threads need to access the same resources, you should lock the
resource to prevent conflicts and inaccurate calculations.
Key Terms
Do you know what these key terms mean? You can check your answers by looking up
the terms in the glossary at the end of the book.
Q Multithreaded
Q Thread
Q Thread-safe
Chapter 7 Review 311

Case Scenarios
In the following case scenarios, you apply what you’ve learned about how to imple-
ment and apply multithreading. You can find answers to these questions in the
“Answers” section at the end of this book.
Case Scenario 1: Print in the Background
You are an application developer for City Power & Light, and you are adding the abil-
ity to print reports to an existing application. Your manager provides you with some
basic requirements:
Q Write a method named Print that accepts a PrintJob object.
Q Print in the background to allow the user to continue working with the applica-
tion’s user interface.
Q Write the simplest code possible.
Questions
Answer the following questions for your manager:
1. What’s the easiest way to print the report?
2. Within the Print method, how can you access the PrintJob object?
3. If you need to display whether the print job succeeded later, how can you do it?
Case Scenario 2: Ensuring Integrity in a Financial Application
You are an application developer working for Humongous Insurance, and you are cre-
ating an application that accepts financial transactions from thousands of cash regis-
ters. The application is multithreaded, and if two cash registers submit a transaction
at the same time, multiple threads can be running simultaneously. Accuracy is critical.
Questions
Answer the following questions for your manager:
1. The application maintains an object instance that tracks the total number of
transactions. For every transaction, you need to increment the value of the
object. How should you do that?
312 Chapter 7 Review
2. Most transactions require that you debit one account and credit another
account. While the debit and credit takes place, you must ensure no other trans-

actions occur. How can you do this?
Suggested Practices
To master the “Implementing service processes, threading, and application domains
in a .NET Framework application” exam objective, complete the following tasks.
Develop Multithreaded .NET Framework Applications
For this task, you should complete at least Practices 1, 2, and 3. If you want a better
understanding of how thread priorities affect performance, complete Practice 4
as well.
Q Practice 1 Add multithreaded capabilities to a real-world application that you’ve
written. Look for methods that prevent the user from interacting with the user
interface, long-running methods that the user might want to cancel, processor-
intensive tasks that can be distributed between multiple threads (and thus run
on multiple processors simultaneously), and methods that need to wait for net-
work connections.
Q Practice 2 Using a multithreaded real-world application, look for potential
resource access conflicts. Add locking as required to ensure resources are never
overwritten.
Q Practice 3 Create a Windows Presentation Foundation (WPF) application.
When the user clicks a Start button, begin a processor-intensive task in a back-
ground thread, such as calculating the value of pi. (You can find algorithms by
searching the Internet.) Continue calculating until the user clicks a Stop button,
and then display the results to the user.
Q Practice 4 To understand how thread priority affects performance, write an
application that includes a method to calculate the value of pi. Create two Thread
instances: one that calls the pi calculation method using the Highest priority, and
a second that calls the pi calculation method using Normal priority. Notice how
much farther the Highest priority thread gets in the calculation in a given amount
of time.
Chapter 7 Review 313
Take a Practice Test

The practice tests on this book’s companion CD offer many options. For example, you
can test yourself on just the content covered in this chapter, or you can test yourself on
all the 70-536 certification exam content. You can set up the test so that it closely sim-
ulates the experience of taking a certification exam, or you can set it up in study mode
so that you can look at the correct answers and explanations after you answer each
question.
MORE INFO Practice tests
For details about all the practice test options available, see the section “How to Use the Practice
Tests,” in the Introduction to this book.

315
Chapter 8
Application Domains and Services
This chapter covers two distinct topics: application domains and services. Application
domains enable you to call external assemblies with optimal efficiency and security.
Services are a special type of assembly that runs in the background, presents no user
interface, and is controlled by using special tools. This chapter discusses how to create
and configure application domains, and how to develop and install services.
Exam objectives in this chapter:
Q Create a unit of isolation for Common Language Runtime (CLR) in a .NET
Framework application by using application domains
Q Implement, install, and control a service
Lessons in this chapter:
Q Lesson 1: Creating Application Domains . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
Q Lesson 2: Configuring Application Domains . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
Q Lesson 3: Creating Windows Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
Before You Begin
To complete the lessons in this chapter, you should be familiar with Microsoft Visual
Basic or C# and be comfortable with the following tasks:
Q Creating a Console application in Microsoft Visual Studio using Visual Basic or C#

Q Adding namespaces and system class library references to a project
Q Creating text files
Q Adding events to the event log
316 Chapter 8 Application Domains and Services
Lesson 1: Creating Application Domains
Developers often need to run an external assembly. However, running an external
assembly can lead to inefficient resource usage and security vulnerabilities. The best
way to manage these risks is to create an application domain and call the assembly
from within the protected environment.
After this lesson, you will be able to:
Q Describe the purpose of an application domain
Q Write code that uses the AppDomain class
Q Create an application domain
Q Start an assembly within an application domain
Q Unload the application domain
Estimated lesson time: 20 minutes
What Is an Application Domain?
An application domain is a logical container that allows multiple assemblies to run
within a single process but prevents them from directly accessing memory that
belongs to other assemblies. In addition, application domains provide isolation from
faults because unhandled exceptions do not affect other application domains, which
allows applications in other application domains to continue running undisturbed.
Another benefit of using multiple application domains is that each application
domain can be assigned a different security access level (even though it might run in
the same process as other application domains).
Application domains offer many of the features of a process, such as separate memory
spaces and separate access to resources. However, application domains are more effi-
cient than processes, enabling multiple assemblies to be run in separate application
domains without the overhead of starting separate processes. Figure 8-1 shows how a
single process can contain multiple application domains.

If an application runs with full trust, the application domain is not a secure boundary.
Applications with full trust can bypass .NET Framework security checks by calling
native code, which in turn can gain unrestricted access to anything within the process
(and thus within any application domain).
Lesson 1: Creating Application Domains 317
Figure 8-1 Application domains keep assemblies separate within a single process
IMPORTANT Contrasting Application Domains and Processes
The .NET Framework runtime manages application domains, whereas the operating system
manages processes.
The best example of application domains in use today is the Microsoft Internet Infor-
mation Services (IIS) ASP.NET worker process, implemented by w3wp.exe. If you
have 10 ASP.NET applications on a single Web server, all applications can run within
the same process. However, ASP.NET creates a separate application domain for each
application, preventing each application from accessing another application’s data.
If two applications need to communicate, you need to use .NET remoting, Web
services, or a similar technique.
Most of the time, you rely on the existing runtime hosts to create application
domains for your assemblies automatically. Examples of runtime hosts built into
Microsoft Windows are ASP.NET, Windows Internet Explorer (which creates a single
application domain for all assemblies from a specific Web site), and the operating
system. You can configure the behavior of these application domains by using
friendly tools such as the Internet Information Services Manager and the .NET
Framework Configuration tool.
However, just as w3wp.exe creates application domains to isolate multiple instances
of an assembly, you can create your own application domains to call assemblies with
little risk that the assembly will take any action or access any resources that you have
not specifically permitted. Figure 8-2 shows how an assembly can host application
domains.
Assembly Assembly Assembly
Application domain Application domain

Operating system
Process
.NET Framework runtime
318 Chapter 8 Application Domains and Services
Figure 8-2 Assemblies can host child application domains
Besides isolating an assembly for security reasons, you can use application domains to
improve reliability and efficiency.
Reliability
Use application domains to isolate tasks that might cause a process to terminate. If the
state of the application domain that’s executing a task becomes unstable, the application
domain can be unloaded without affecting the process. This technique is important
when a process must run for long periods without restarting. You can also use applica-
tion domains to isolate tasks that should not share data. For example, if your application
supports add-ins, you can load the add-ins into a separate application domain and
unload it whenever necessary without affecting the parent application domain.
Efficiency
If an assembly is loaded into the default application domain, the assembly cannot be
unloaded from memory while the process is running. However, if you open a second
application domain to load and execute the assembly, the assembly is unloaded when
that application domain is unloaded. Use this technique to minimize the working set
of long-running processes that occasionally use large dynamic-link libraries (DLLs).
The AppDomain Class
Application domains are implemented in the .NET Framework using the System
.AppDomain class. To use an application domain, create an instance of the App-
Domain class and then execute an assembly within that domain. Table 8-1 shows
the AppDomain properties.
.NET Framework runtime
Application domain
Assembly
Application domain

Application domain
Assembly
Lesson 1: Creating Application Domains 319
Table 8-1
AppDomain Properties
Name Description
ActivationContext Gets the activation context for the current application
domain.
ApplicationIdentity Gets the identity of the application in the application
domain.
ApplicationTrust Gets information describing the permissions granted to an
application and whether the application has a trust level that
allows it to run.
BaseDirectory Gets the base directory that the assembly resolver uses to
probe for assemblies.
CurrentDomain Gets the current application domain for the current thread.
This property allows you to analyze the current domain to
determine context or verify permissions.
DomainManager Gets the domain manager that was provided by the host
when the application domain was initialized.
DynamicDirectory Gets the directory that the assembly resolver uses to probe
for dynamically created assemblies.
Evidence Gets the Evidence associated with this application domain
that is used as input to the security policy. For more
information about evidence, refer to Chapter 11, “Application
Security.”
FriendlyName Gets the friendly name of this application domain.
For domains created by the .NET Framework, this friendly
name takes the form <ProjectName>.vshost.exe. You must
specify the friendly name when you create application

domains programmatically.
Id Gets an integer that uniquely identifies the application
domain within the process.
RelativeSearchPath Gets the path relative to the base directory where the
assembly resolver should probe for private assemblies.
320 Chapter 8 Application Domains and Services
Table 8-2 shows the most important AppDomain methods.
SetupInformation Gets the application domain configuration information for
this instance.
ShadowCopyFiles Gets an indication whether all assemblies loaded in the
application domain are shadow copied.
Table 8-2 AppDomain Methods
Name Description
ApplyPolicy Returns the assembly display name after a policy
has been applied.
CreateComInstanceFrom Creates a new instance of a specified COM type.
CreateDomain Creates a new application domain. Use this
method instead of an AppDomain constructor.
CreateInstance Creates a new instance of a specified type defined
in a specified assembly.
CreateInstanceAndUnwrap Creates a new instance of a specified type.
CreateInstanceFrom Creates a new instance of a specified type defined
in the specified assembly file.
CreateInstanceFromAndWrap Creates a new instance of a specified type defined
in the specified assembly file.
DefineDynamicAssembly Defines a dynamic assembly in the current
application domain.
DoCallBack Executes the code in another application domain
that is identified by the specified delegate.
ExecuteAssembly Executes the assembly contained in the

specified file.
ExecuteAssemblyByName Executes an assembly.
Table 8-1 AppDomain Properties
Name Description

×