Home > Articles

Advanced CLR Topics

This chapter is from the book

In this chapter

  • Threading in .NET
  • Exception Handling in .NET
  • The MSIL Disassembler
  • From Here

Visual Studio .NET introduces the Common Language Runtime (CLR). As discussed in Chapter 16, "The Common Language Runtime," the CLR establishes an environment that allows the developer to create applications that can be used in a hosted, managed code space. This allows the developer to create portable, secure applications that will run on any system that hosts the CLR. Two topics are important to constructing applications in the CLR and bear further inspection: Threading in .NET and Exception Handling in .NET. We will also be looking at ILDASM, a tool that comes with Visual Studio .NET and helps the developer to understand managed code applications.

Threading in .NET

Threading for native code C++ applications was introduced in Chapter 14, "Multitasking with Windows Threads". Using threads in the .NET Framework is similar in concept to using them in C++ applications, but different in implementation. The .NET Framework uses the classes contained in the System::Threading namespace to help the developer create and manage threads. In this section we will focus on the differences of creating, using and destroying threads in a .NET application.

Overview of Threads in Managed Code

When using threads in a .NET application, all of the functions necessary to work with the threads are contained in the System::Threading namespace. The System::Threading namespace implements the Thread class whose members are similar in function to the Win32 API that C++ uses in the native code implementation of threads. Table 25.1 shows the relationship of some of the methods in System::Threading to the corresponding functions in the Win32 API.

Table 25.1 Threading Functions in .NET and Win32

System::Threading

Win32 API

Thread Creation:

Thread

CreateThread

ThreadStart

 

Thread Destruction:

Thread.Abort

TerminateThread

Thread Management:

Thread.Priority

SetThreadPriority

Thread.Join

WaitForSingleObject

Thread.Sleep

Sleep

Thread.Suspend

SuspendThread

Thread.Resume

ResumeThread


NOTE

There are some Win32 functions (like ExitThread) and System::Threading classes (like Thread.Name and Thread.IsBackground) that do not have equivalent functions or classes in the other corresponding implementation of threading.

CAUTION

When using this table, be sure you understand the side effects of a particular function or class. They may not map directly from one to the other and would require additional changes to your application to work properly with your design.

For example, Thread.Abort and TerminateThread do not exactly match each other in functionality. TerminateThread will remove a thread from execution and does not perform any cleanup of the thread's resources nor is it preventable. However, Thread.Abort follows the conventions of managed code: it will clean up the thread resources and can be prevented if the thread implements ResetAbort.

Creating a Thread in Managed Code

Creating a thread using the Common Language Runtime is simple. The Thread class has methods necessary to create, manage and destroy threads. To illustrate the ease of creating a thread in an application, this chapter builds a simple console based Managed C++ application that creates and uses a single thread.

First, create a Managed C++ project. Start Visual Studio .NET and select File, New, Project to bring up the New Project dialog box as shown in Figure 25.1.

Figure 25.1 The New Project dialog box.

In the Project Types area on the left side of the dialog box, select Visual C++ Projects. Select a Managed C++ Application on the right hand side and enter ThreadEx as the name of the project. When you have done all this and the dialog looks similar to Figure 25.2, then click OK to create the project.

Figure 25.2 Completed New Project dialog box for the ThreadEx sample application.

After the project is created, open the ThreadEx.cpp file using the Solution Explorer. This file is the .cpp file created for your application. When it is opened you will see the basic code skeleton for a Managed C++ console application.

In order to use the threading classes without typing their full name each time, add the System::Threading namespace to the application. Add the following code after the using statement for the System namespace:

using namespace System::Threading;

Adding this line of code allows the application to refer to the classes and methods defined in the System::Threading namespace with their shorter names, such as Thread.

A thread executes a function, and in Managed C++ it's a member function of a class. Enter the following class definition after the declaration for the System::Threading namespace.

public __gc class SimpleThreadClass
{
 public:
  // The method that will be called when the thread is started.
  void ThreadCounterProc()
  {
   Console::WriteLine(S"Worker thread invoked, will count to 10.");
   for(int x=1; x!=11; x++)
    Console::WriteLine(x);
   Console::WriteLine(S"Worker thread is terminating.");
  }
};

The declaration of the class is simple enough except for the new keyword __gc. This keyword is part of the Managed extensions for C++ that have been added to the language to support the Common Language Runtime. The __gc keyword denotes that the class will be managed by the Common Language Runtime and that it is garbage collected. This means that a specific call to destroy the class is not necessary and that the class will be cleaned up by the Common Language Runtime at the end of its lifetime. This is discussed in more detail in Chapter 20, "Managed and Unmanaged Code."

The rest of the class defines the member function, ThreadCounterProc()that is the core of the processing that the thread will do. The method is defined as a public method and does not have a return value. It begins by writing to the console that the thread has begun and then proceeds to count to 10. Each iteration of the for loop is written to the console so that the user can see this happening. Finally, the notice that the thread is terminating is displayed and then the thread will cease execution.

Next, add the code to the _tmain function to create the thread object, invoke the thread and complete the processing of the worker thread and the main application. Delete the two lines provided in the original project in the _tmain function between the function declaration and the return statement. Then add the following code to the application.

Console::WriteLine(S"Main application thread is starting.");
SimpleThreadClass *objSimpleThread = new SimpleThreadClass();

//Create the thread object
Thread *objWorkerThread = new Thread(new ThreadStart(objSimpleThread,
         &SimpleThreadClass::ThreadCounterProc));

//Start the thread
objWorkerThread->Start();

Console::WriteLine(S"Main application thread is terminating.");

The _tmain function defines the main thread of execution for the application. Did I mention that an application always contains at least one thread? The worker thread created for the new Thread object will execute independently of the main application thread but lives in the same process space as the main application thread. Only when all threads of an application are terminated will the process close and the operating system clean up the resources of the process.

The first line of the application writes to the console window that the application's main thread is beginning execution. This is followed by a declaration and initialization of a pointer to a SimpleThreadClass. Now we come to the meat of the thread creation and invocation. It declares a Thread object pointer called objWorkerThread that will be used to manipulate the thread. In the same statement it constructs the objWorkerThread.

Let's take a closer look at that statement:

new Thread(new ThreadStart(objSimpleThread, &SimpleThreadClass::ThreadCounterProc));

The constructor's basic form uses a delegate function (ThreadStart) with two parameters. A delegate is the Common Language Runtime version of the function pointer in C++. The first parameter is the pointer to the thread class object. The second is the address of the ThreadCounterProc in the SimpleThreadClass object. This delegate function constructor then returns the necessary information into the Thread constructor so that when the thread starts, the Thread object knows what method of the SimpleThreadClass to invoke. The same initialization code could also have been written as:

ThreadStart *MyThreadStart = new ThreadStart(objSimpleThread, 
            &SimpleThreadClass::ThreadCounterProc);
Thread *objWorkerThread = new Thread(MyThreadStart);

This shows more clearly the relationship of the two method calls.

The next statement invokes the thread and begins its executionby calling Start, a member function objWorkerThread inherited from the Thread class. Then the main thread of execution continues, writes to the console that it is complete, and terminates.

The new thread executes parallel to the main thread of execution does its work by executing the function.

After entering the code into the ThreadEx.cpp file, build it and run it.

The program output may at first surprise you. Here is an example of the output:

Main application thread is starting.
Main application thread is terminating.
Worker thread invoked, will count to 10.
1
2
3
4
5
6
7
8
9
10
Worker thread is terminating.
Press any key to continue

Notice that the main application thread starts and ends before any worker thread output is produced. This is because the main thread of execution does not wait until the worker thread is complete before terminating. The worker thread's processing is in parallel to the main thread and it does not matter if the main thread is terminated[md]the worker thread will continue to do its job. You are unlikely to want this behavior in a real application because, most likely, the worker thread is doing some processing that the main thread will want later. Therefore, it would not be wise to terminate the main thread before the worker thread is done with its processing. These are the same threading issues developers tackled in C++ in the unmanaged world, and you can use the same types of mechanisms to solve the problem in the managed world.

CAUTION

Starting a thread is not like making a function call. Do not expect the thread call to stop the execution of the main thread while the worker thread is doing its work. The worker thread is executing in parallel to the main thread and operates independently for the most part.

Threads are an excellent way to take separate tasks that may need to run concurrently and have the computer work on them all at the same time. However, threads are usually not a good choice when one item needs to be completed before another can take place.

Using threads in Managed C++ is very simple and it is easy to add additional threads by creating additional worker thread objects and starting them as in the preceeding example. Sometimes it is necessary to place priority of one thread over another. How to do that is the subject of the next section.

Setting Thread Priority

When you are working with many threads, it may be necessary to change the thread's priority in the application. There are several different levels of priority that can be used in scheduling threads. However, like many things with multitasking, the thread priority does not tell the compiler in which order the threads are to be executed. It provides a clue to the Common Language Runtime what the relative importance of a thread compared with others that the application is running. Therefore, when setting thread priority, the question to ask yourself is not "What thread do I want to run first?" but "What thread is more important to process relative to the others that the application has?"

The priority of a thread is stored in the Priority property of the Thread object. This value can be changed by the application in order to make sure that a thread gets scheduling priority over another. You set the thread priority after the Thread object is instantiated but before the thread begins execution. Table 25.2 shows the values of the ThreadPriority enum that is set in the Priority property.

Table 25.2 ThreadPriority Values

Value

Description

Highest

Thread will be scheduled before all other priorities.

Above Normal

Thread will be scheduled between threads of Highest and Normal priorities.

Normal (Default)

Thread will be scheduled between threads of Above Normal and Below Normal priorities.

Below Normal

Thread will be scheduled between threads of Normal and Lowest priorities.

Lowest

Thread will be scheduled after all other priorities.


Using the ThreadPriority is simple. Create another solution called ThreadPriEx[md]again a Managed C++ application. To the ThreadPriEx.cpp file, add the code from Listing 25.1.

Listing 25.1 The ThreadPriEx.cpp Sample

#include "stdafx.h"

#using <mscorlib.dll>
#include <tchar.h>

using namespace System;
using namespace System::Threading;

public __gc class SimpleThreadClass1
{
 public:
  // The method that will be called when the thread is started.
  void ThreadCounterProc()
  {
   Console::WriteLine(S"Worker thread #1 started, will count to 10.");
   for(int x=1; x!=11; x++)
    Console::WriteLine(x);
   Console::WriteLine(S"Worker thread #1 is terminating.");
  }
};

public __gc class SimpleThreadClass2
{
 public:
  // The method that will be called when the thread is started.
  void ThreadCounterProc()
  {
   Console::WriteLine(S"Worker thread #2 started, will count to 10.");
   for(int x=1; x!=11; x++)
    Console::WriteLine(x);
   Console::WriteLine(S"Worker thread #2 is terminating.");
  }
};

public __gc class SimpleThreadClass3
{
 public:
  // The method that will be called when the thread is started.
  void ThreadCounterProc()
  {
   Console::WriteLine(S"Worker thread #3 started, will count to 10.");
   for(int x=1; x!=11; x++)
    Console::WriteLine(x);
   Console::WriteLine(S"Worker thread #3 is terminating.");
  }
};

// This is the entry point for this application
int _tmain(void)
{
 Console::WriteLine(S"Main application thread is starting.");
 SimpleThreadClass1 *objSimpleThread1 = new SimpleThreadClass1();
 SimpleThreadClass2 *objSimpleThread2 = new SimpleThreadClass2();
 SimpleThreadClass3 *objSimpleThread3 = new SimpleThreadClass3();

 //Create the thread objects
 Thread *objWorkerThread1 = new Thread(new ThreadStart(objSimpleThread1, &SimpleThreadClass1::ThreadCounterProc));
 Thread *objWorkerThread2 = new Thread(new ThreadStart(objSimpleThread2, &SimpleThreadClass2::ThreadCounterProc));
 Thread *objWorkerThread3 = new Thread(new ThreadStart(objSimpleThread3, &SimpleThreadClass3::ThreadCounterProc));

 objWorkerThread3->Priority=ThreadPriority::Highest;
 objWorkerThread2->Priority=ThreadPriority::Normal;
 objWorkerThread1->Priority=ThreadPriority::Lowest;

 //Start the thread
 objWorkerThread1->Start();
 objWorkerThread2->Start();
 objWorkerThread3->Start();

 Console::WriteLine(S"Main application thread is terminating.");
 return 0;
}

This code is very similar to the ThreadEx sample discussed earlier, but instead of one SimpleThreadClass there are now three that are similar in design. The creation of the Thread object is also very similar. The big difference is where we make the thread priority assignments:

 objWorkerThread3->Priority=ThreadPriority::Highest;
 objWorkerThread2->Priority=ThreadPriority::Normal;
 objWorkerThread1->Priority=ThreadPriority::Lowest;

To change the schedule priority of the thread we make a call to the ThreadPriority member function of each Thread object and assign it to the Priority property. We then start each thread and then conclude the execution of the main thread.

After you build the solution, execute it and you should see the following:

Main application thread is starting.
Main application thread is terminating.
Worker thread #3 started, will count to 10.
1
2
3
4
5
6
7
8
9
10
Worker thread #3 is terminating.
Worker thread #2 started, will count to 10.
1
2
3
4
5
6
7
8
9
10
Worker thread #2 is terminating.
Worker thread #1 started, will count to 10.
1
2
3
4
5
6
7
8
9
10
Worker thread #1 is terminating.
Press any key to continue

So even though WorkerThread1 was started first WorkerThread3 was the first to complete its work. This is because the priority of the WorkerThread3 was set higher than WorkerThread1.

NOTE

Thread priority is a suggestion to the operation system on how to allocate processing time and priority of scheduling to a thread. Higher priority threads are scheduled to run first and receive more processing time than lower priority threads. But since this is only a suggestion, if the Common Runtime Language deems it necessary to lower the priority of a thread temporarily then it has the ability to do so. Normally, when a thread priority is lowered in this manner then it will be scheduled more as expected later in execution if possible.

CAUTION

While it is often very useful to schedule threads to higher or lower priorities, do not do so unless the task that the thread is running really requires the change in priority. Normal priority is fine for most purposes. If you schedule too many Highest priority threads, it is possible to starve other threads and processes for CPU time. This would have the net effect of making the whole application appear slower when judicial use of thread priority could actually make the application run faster.

Destroying Threads

Sometimes it is necessary to terminate the execution of a thread. Before managed code, this was not an easy task and was not recommended because of the issues with interruption of execution and housekeeping. Now with garbage collection in managed code it is much easier.

You have already used the most common method of thread destruction, which is to let the thread terminate normally when the thread is done executing. The thread object and all thread specific allocations are cleaned up by the Common Runtime Language. However, sometimes it is necessary to terminate a thread before it finishes normally. The Common Language Runtime Thread class has a method that you can use to terminate a thread. It is Thread::Abort.

Thread::Abort allows for a smooth termination of the thread. Managed code does not support a method of thread termination that is similar to the Win32 API TerminateThread(). TerminateThread would stop the execution of a thread dead in its tracks, however, that is not how objects should behave in the CLR. When you use Thread::Abort, the thread will stop when it has reached a point where garbage collection can take place. Only then will it stop execution.

As an example, here is what the ThreadEx sample could look like if we decided to terminate the thread execution right away.

int _tmain(void)
{
 Console::WriteLine(S"Main application thread is starting.");
 SimpleThreadClass *objSimpleThread = new SimpleThreadClass();

 //Create the thread object
 Thread *objWorkerThread = new Thread(new ThreadStart(objSimpleThread, &SimpleThreadClass::ThreadCounterProc));

 //Start the thread
 objWorkerThread->Start();

 //Oops, we really don't want that thread to run after all
 Console::WriteLine(S"Main application is terminating thread.");
 objWorkerThread->Abort();
 Console::WriteLine(S"Main application thread is terminating.");
 return 0;
}

When the call to Thread::Abort is made, the Common Language Runtime will throw a ThreadAbortException. This exception cannot be caught by a managed code application, but the Common Language Runtime will execute any __finally clauses that it finds when it winds the stack, thus allowing you to perform any cleanup that is necessary for your application (not necessarily by the thread object or its local properties or members).

You will not be notified when the thread is terminated but if it is necessary for you to know, you can use Thread::Join to tell you when the thread has been destroyed.

Thread Pooling

While you can create, schedule and destroy threads in managed code using the Thread class, the recommended way to use threads in managed code is something else entirely, called a ThreadPool.

ThreadPools have been around for a while but have not been very easy to use. Now with the Common Language Runtime, ThreadPool is far easier to use than the Thread class. There is no thread object management that the application must do and each application gets its own ThreadPool object to play with. So what exactly is a ThreadPool and why did we just spend the past while talking about the Thread object?

A typical application will not use threads very often and only for short term tasks. After the task is complete, the thread goes into a sleeping state and waits to be used again. This uses resources and requires management on the part of the application. Wouldn't it be nice if there was a way that you could call a thread only when you needed it and then when it was done you could forget about it? Well, that is a ThreadPool. You call ThreadPool::QueueUserWorkItem and pass it a WaitCallback delegate function then when the system is ready, it will create a thread, assign the thread to do the work of the function that you listed as the callback with QueueUserWorkItem and then clean up the thread when it is done.

Threads that are created from a ThreadPool are not like threads created from Thread. The most important difference is that they are by default a background thread and not a foreground thread. In the managed code world, a background thread will only execute as long as the main thread of execution is still running. So, if you terminate the main thread before the ThreadPool thread is done, then it will be terminated as well. Normally, this is not much of an issue but it is something to be aware of. Also, You cannot set the priority of a ThreadPool thread, and all ThreadPool threads are run in the COM multithreaded apartment model. This may seem like a lot of limitations but remember that the main purpose of a ThreadPool thread is to handle asynchronous types of tasks. These are usually things that need to be done without being tracked.

Here's an example similar to the ThreadEx sample that uses a ThreadPool object thread instead of a Thread object thread. Create a Managed C++ application as before and name it ThreadPoolEx. Enter the code in Listing 25.2.

Listing 25.2 The ThreadPoolEx Sample

#include "stdafx.h"

#using <mscorlib.dll>
#include <tchar.h>

using namespace System;
using namespace System::Threading;

public __gc class SimpleThreadClass
{
 public:
  // The method that will be called when the thread is started.
  static void ThreadCounterProc(Object *)
 {
   Console::WriteLine(S"Worker thread invoked, will count to 10.");
   for(int x=1; x!=11; x++)
    Console::WriteLine(x);
   Console::WriteLine(S"Worker thread is terminating.");
 }
};

// This is the entry point for this application
int _tmain(void)
{
 Console::WriteLine(S"Main application thread is starting.");
 SimpleThreadClass *objSimpleThread = new SimpleThreadClass();

 //Start the thread
 ThreadPool::QueueUserWorkItem(new WaitCallback(objSimpleThread, &SimpleThreadClass::ThreadCounterProc));

 Thread::Sleep(1000);
 Console::WriteLine(S"Main application thread is terminating.");
 return 0;
}

You will notice that the code is slightly shorter than the code for ThreadEx. Here is how it works. First, the SimpleThreadClass is basically the same. There is one important difference and that is the declaration of the ThreadCounterProc. Now it takes a parameter of Object * and is a static void function. This is important because all functions that are registered with the WaitCallback method have to match that method's declaration. In other words, all threads that use a ThreadPool will be constructed in a similar fashion. You need not pass an object pointer but it is there if you need to.

The line that does all the work for the queuing of the thread is:

ThreadPool::QueueUserWorkItem(new WaitCallback(objSimpleThread, 
         &SimpleThreadClass::ThreadCounterProc));

A call to the ThreadPool method QueueWorkItem includes the construction of a callback function -- the function of our thread. This is done by calling WaitCallback with the pointer to our thread object and the entry point for the thread to begin execution.After that, just sit back and wait for execution to begin.

Now, because the thread is run in the background, it is necessary to have the main thread sleep for some time to allow the worker thread to execute, which is why there is a call to Thread::Sleep afterwards. In a normal application, you could use other methods to signal the main thread when the execution is done so that it would not terminate too soon.

ThreadPools are certainly easy to use and will most likely be the first choice for most applications. It is lightweight for your application, managed by the Common Runtime Language and suitable to most purposes. However, you also have the flexibility to use the Thread object when you need to have tighter control of the threads in your application.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020