Home > Articles > Home & Office Computing > Microsoft Windows Desktop

This chapter is from the book

Composite Activity Execution

Enough with WF programs that are only a single activity! It's time to develop some composite activities and then declare and run some more interesting WF programs.

We will begin with the Sequence activity of Chapter 2, shown again here:

public class Sequence : CompositeActivity
  protected override ActivityExecutionStatus Execute(

  ActivityExecutionContext context)
  if (this.EnabledActivities.Count == 0)
    return ActivityExecutionStatus.Closed;

  Activity child = this.EnabledActivities[0];
  child.Closed += this.ContinueAt;

  return ActivityExecutionStatus.Executing;

void ContinueAt(object sender,
  ActivityExecutionStatusChangedEventArgs e)
  ActivityExecutionContext context =
    sender as ActivityExecutionContext;

  e.Activity.Closed -= this.ContinueAt;
  int index = this.EnabledActivities.IndexOf(e.Activity);

  if ((index + 1) == this.EnabledActivities.Count)

    Activity child = this.EnabledActivities[index + 1];
    child.Closed += this.ContinueAt;

The job of the Sequence activity is to emulate a C# {} statement block, and execute its child activities one by one. Only when the final child activity of a Sequence finishes can the Sequence report that it is complete.

The Execute method of Sequence first checks to see if there are any child activities at all. If none are present, the method returns ActivityExecutionStatus.Closed. The Sequence is done because it has nothing to do. It is like an empty C# statement block. If one or more child activities are present, though, the first child activity needs to be scheduled for execution. In order to do this, two lines of code are necessary:

child.Closed += ContinueAt;

These two statements constitute a very simple bookmarking pattern that you will encounter repeatedly in composite activity implementations. The subscripton to the Closed event of the child activity sets up a bookmark that is managed internally by the WF runtime. The Activity.Closed event is merely syntactic sugar on top of the bookmark management infrastructure. The += results in the creation of a bookmark, and the dispatch of the Closed event (the resumption of the bookmark), is brokered via the scheduler.

The invocation of ActivityExecutionContext.ExecuteActivity requests that the indicated child activity be scheduled for execution. Specifically, the Execute method of the child activity is added as a work item in the scheduler work queue.

In order to enforce the activity automaton, the WF runtime will throw an exception from within ExecuteActivity if the child activity is not in the Initialized state. If the call to ExecuteActivity succeeds, an item is added to the scheduler work queue, representing the invocation of the child activity's Execute method. A successful call to ExecuteActivity also immediately places the child activity in the Executing state.

The Sequence activity's code that schedules the execution of its first child activity and subscribes for this child activity's Closed event is analogous to the ReadLine activity's logic that creates a WF program queue and subscribes to that queue's QueueItemAvailable event. In both cases, the activity is dependent upon some work, outside of its control, and can proceed no further until it is notified that this work has been completed. The code is somewhat different, but the bookmarking pattern is exactly the same.

Of course, for a composite activity like Sequence, the pattern must be repeated until all child activities have completed their execution. This is achieved in the ContinueAt method, which is scheduled for execution when the currently executing child activity moves to the Closed state. When it receives notification that a child activity has completed its execution, Sequence first removes its subscription for that child activity's Closed event. If the child activity that just completed is the last child activity in the Sequence, the Sequence reports its own completion. Otherwise, the bookmarking pattern is repeated for the next child activity.

There are a couple of crucial aspects to the WF runtime's role as the enforcer of state transitions. If the Sequence activity tries to report its completion while a child activity is in the Executing state, this transition will not be allowed. This fact is the cornerstone of the WF runtime's composition-related enforcement (and is not implied by the activity automaton).

The corollary to this rule is that only an activity's parent is allowed to request that activity's execution. A call to ActivityExecutionContext.ExecuteActivity by its parent is the only stimulus that will cause an activity to move to the Executing state.

These simple enforcements play a huge role in establishing the meaning and ensuring the integrity of composite activities and, by extension, WF programs.

Of course, there must be one exception to the rule that only the parent of an activity can schedule its execution, and that is for the root activity, whose Parent property is null. As we have already seen, it is the application hosting the WF runtime that makes a request to the WF runtime to schedule the execution of the root activity's Execute method.

Effectively, as part of the creation of a WF program instance, the WF runtime creates an implicit bookmark whose resumption point is the Execute method of the root activity. The invocation of WorkflowInstance.Start resumes this bookmark, and begins the execution of the program instance.

It will be instructive to trace the execution of a simple WF program that uses Sequence, noting the changes that occur at each step to the scheduler work queue. The XAML in Listing 3.15 is a Sequence with a set of WriteLine child activities.

Listing 3.15. A WF Program That Uses Sequence

<Sequence x:Name="s1" xmlns="http://EssentialWF/Activities"
  <WriteLine x:Name="w1" Text="One" />
  <WriteLine x:Name="w2" Text="Two" />
  <WriteLine x:Name="w3" Text="Three" />
  <WriteLine x:Name="w4" Text="Four" />

Running an instance of this program will result in the expected output.


When the application hosting the WF runtime calls WorkflowInstance.Start, it is telling the WF runtime to resume the initial, implicit bookmark. The result of the call to Start is that the scheduler work queue for this instance contains a single item—a work item for the Execute method of the root activity.

The root activity—in our example, the Sequence—is now in the Executing state, even though its Execute method has not actually been called. Figure 3.10 shows the scheduler work queue, along with the state of the WF program instance (with Executing activities shown in boldface).

Figure 3.10

Figure 3.10 WF program instance after WorkflowInstance.Start

At this point, the WF runtime's dispatcher logic enters the picture, and invokes the Sequence activity's Execute method, removing the corresponding item from the scheduler work queue. From this point forward, it is the activities in the WF program that drive the program forward; the WF runtime plays a passive role as the scheduler of work and the enforcer of activity state transitions.

The Execute method of Sequence will, as we know, schedule its first child activity for execution. When the Execute method returns, the scheduler work queue looks as it is shown in Figure 3.11. The first WriteLine activity is now in the Executing state (again, even though its Execute method has not been called). The Sequence too is in the Executing state.

Figure 3.11

Figure 3.11 WF program instance after Sequence.Execute

As we know from the basic pattern used for child activity execution, Sequence has, at this point, also subscribed to the Closed event of its first child activity. Even though Closed (and the other events defined on the Activity class) looks like a normal event, under the covers it is an internally managed bookmark.

When the Execute method of WriteLine returns, the WriteLine activity moves to the Closed state. Because the Sequence has subscribed to the event corresponding to this transition, an appropriate work item will be placed in the scheduler work queue. The current state of the program instance is as shown in Figure 3.12; the first WriteLine is underlined to indicate that it has completed its execution and is in the Closed state.

Figure 3.12

Figure 3.12 WF program instance after first child activity completes

Now the work item for the ContinueAt method of Sequence is dispatched. As we know, ContinueAt will follow the standard pattern for requesting the execution of the second child activity. When ContinueAt method returns, the program state is as shown in Figure 3.13, with the second WriteLine activity now in the Executing state.

Figure 3.13

Figure 3.13 WF program instance after first callback to Sequence.ContinueAt

This pattern will continue as the Sequence marches through the list of its child activities. When the last child activity reports its completion, the ContinueAt method will report the completion of the Sequence. The WF runtime will observe this (you can think of the WF runtime as a subscriber to the root activity's Closed event), and will do the necessary bookkeeping to complete this WF program instance.

Figure 3.14 summarizes the execution of our WF program as an interaction diagram.

Figure 3.14

Figure 3.14 Interaction diagram of the execution of Listing 3.15

One crucial point about the Sequence activity is that it implemented sequential execution of its child activities using the general-purpose methods and events available on AEC and Activity. The WF runtime contains no knowledge of sequential activity execution; it only pays attention to the activity automaton and the containment relationships between activities in its role as enforcer of state transitions.

To see how easy it is to define other forms of control flow as composite activities, let's write a composite activity that executes its child activities in an interleaved manner.

The Interleave activity shown in Listing 3.16 implements an AND join by first scheduling the execution of all child activities in a single burst and waiting for them all to complete before reporting its own completion.

Listing 3.16. Interleave Activity

using System;
using System.Collections;
using System.Workflow.ComponentModel;

namespace EssentialWF.Activities
  public class Interleave : CompositeActivity
    protected override ActivityExecutionStatus Execute(
      ActivityExecutionContext context)
      if (this.EnabledActivities.Count == 0)
        return ActivityExecutionStatus.Closed;

      IList<Activity> shuffled = ShuffleList(EnabledActivities);

      foreach (Activity child in shuffled)
        child.Closed += ContinueAt;

      return ActivityExecutionStatus.Executing;

    void ContinueAt(object sender,
      ActivityExecutionStatusChangedEventArgs e)
      e.Activity.Closed -= ContinueAt;

      ActivityExecutionContext context =
        sender as ActivityExecutionContext;

      foreach (Activity child in this.EnabledActivities)
        if ((child.ExecutionStatus !=
            && (child.ExecutionStatus !=


         // ShuffleList method elided for clarity

We will discuss the finer points of the Interleave activity's execution (which induces a form of pseudo-concurrency) a bit later in this chapter. First, though, let's look at the mechanics of how Interleave executes, just as we did for Sequence.

You can see right away that the code for Interleave is quite similar to that of Sequence. In the Execute method, all of the child activities are scheduled for execution, not merely the first one as with Sequence. In the implementation of ContinueAt, the Interleave reports itself as completed only if all child activities are in the Closed state.

There is one other interesting line of code:

IList<Activity> shuffled = ShuffleList(EnabledActivities);

ShuffleList is presumed to be a private helper method that simply shuffles the list of child activities into some random order. The Interleave activity will work just fine without ShuffleList, but we have added it so that users of Interleave cannot predict or rely upon the order in which child activities are scheduled for execution.

The XAML in Listing 3.17 is an Interleave activity that contains a set of WriteLine child activities.

Listing 3.17. Interleaved Execution of WriteLine Activities

<Interleave x:Name="i1" xmlns="http://EssentialWF/Activities"
  <WriteLine x:Name="w1" Text="One" />
  <WriteLine x:Name="w2" Text="Two" />
  <WriteLine x:Name="w3" Text="Three" />
  <WriteLine x:Name="w4" Text="Four" />

Running the program in Listing 3.17 may result in the following output:


Or the following:


Or the following:


Or any of the other possible orderings of the four strings printed by the four WriteLine activities.

Let's trace through the execution of an instance of this program, showing the scheduler work queue and program state. The program is started exactly like the one we developed earlier with Sequence; an item is placed in the scheduler work queue representing a call to the Execute method of the root activity, and the root activity is placed in the Executing state (see Figure 3.15).

Figure 3.15

Figure 3.15 WF program instance in Listing 3.17 after WorkflowInstance.Start

Let's assume that the call to ShuffleList results in the following ordering of child activities: w2, w4, w1, w3. When the Execute method of Interleave returns, the program state is as shown in Figure 3.16.

Figure 3.16

Figure 3.16 WF program instance in Listing 3.17 after Interleave.Execute

Now all four child activities are queued for execution and all four child activities of the Interleave are in the Executing state. The dispatcher will pick the item from the front of the queue (Execute "w2"). This will cause the Execute method of WriteLine named "w2" to be invoked. When this method returns, "Two" will have been printed to the console and the state of the program is as shown in Figure 3.17.

Figure 3.17

Figure 3.17 WF program instance in Listing 3.17 after WriteLine "w2" completes

As expected, because Interleave has subscribed to the Closed event of each child activity, there is a callback to the ContinueAt method present in the scheduler work queue. This item, however, sits behind three other items—the execution handlers for the Execute methods of w4, w1, and w3. The process outlined for w2 will therefore repeat three more times, resulting in the state shown in Figure 3.18.

Figure 3.18

Figure 3.18 WF program instance in Listing 3.17 after WriteLine "w4" completes

At this point, all four WriteLine activities have completed. The Interleave activity, though, has not actually received any notifications because its work items are still in the scheduler work queue. The four work items in the scheduler work queue are all resumptions of the same bookmark. The resumption point is the ContinueAt method of Interleave; the four work items differ only in the EventArgs data that is the payload of each resumed bookmark.

When the first work item is delivered to Interleave, the logic of the ContinueAt method will determine that all child activities are in the Closed state, so the Interleave itself is reported as complete. When the other three callbacks are subsequently dispatched, the WF runtime observes that the Interleave is already in the Closed state, so the callbacks are not delivered (they are simply discarded); delivery of these callbacks would violate the activity automaton because the Interleave cannot resume execution once it is in the Closed state.

Now, what we have seen in the execution of this WF program is quite a bit different than what we saw for the WF program that used Sequence. Things get even more interesting, though, if each child activity of the Interleave is not a simple activity like WriteLine, but a Sequence (which might contain other Interleave activities). Furthermore, it's clearly not very interesting or useful to simply execute WriteLine activities in an interleaved manner. It is much more realistic for each branch to be performing work that depends upon external input. In this way, the ordering of the execution of activities is determined, in part, by the timing of EnqueueItem operations performed by external code on WF program queues. By modeling these interactions in an Interleave, no branch is blocked by any other (because activities use bookmarks when their execution awaits external stimulus) and the execution of the activities within the branches can interleave.

As we know, the Interleave activity uses an explicit shuffling technique to decide the ordering in which its child activities are scheduled for execution. The influence of Interleave, however, ends there. If a Sequence activity is added as a child activity of an Interleave, the Interleave controls when the Sequence executes, but only the Sequence controls when its child activities are executed.

The XAML in Listing 3.18 is an Interleave with a set of Sequence child activities that contain child activities. The name of the WF program queue created by ReadLine in its Initialize method is the name of the activity. So, four WF program queues will be created during the initialization of a WF program instance, and these WF program queues are named r1, r2, r3, and r4.

Listing 3.18. Interleaved Execution of Sequence Activities

<Interleave x:Name="i1" xmlns="http://EssentialWF/Activities"
  <Sequence x:Name="s1">
    <ReadLine x:Name="r1" />
    <WriteLine x:Name="w1" Text="{wf:ActivityBind r1,Path=Text}" />
    <ReadLine x:Name="r2" />
    <WriteLine x:Name="w2" Text="{wf:ActivityBind r2,Path=Text}" />
  <Sequence x:Name="s2">
    <ReadLine x:Name="r3" />
    <WriteLine x:Name="w3" Text="{wf:ActivityBind r3,Path=Text}" />
    <ReadLine x:Name="r4" />
    <WriteLine x:Name="w4" Text="{wf:ActivityBind r4,Path=Text}" />

We are not going to go through the execution of an instance of this program step by step—it would take a few pages of diagrams—but we know enough about the execution logic of the Sequence and Interleave activities to predict what will happen. Assuming that no items are enqueued into any of the WF program queues, the program will reach the state shown in Figure 3.19.

Figure 3.19

Figure 3.19 WF program instance in Listing 3.18 after reaching ReadLine activities

At this point, the program is idle. Both Sequence activities have started executing, and each has, in turn, requested the execution of their first child activity (which happens to be a ReadLine activity in both cases). Each ReadLine activity is stuck waiting for an item to appear in its WF program queue. If the Interleave had a third child activity that was a Sequence of any number of WriteLine activities, then this Sequence would run to completion.

If we enqueue the string "hello" into WF program queue "r3", there will be an episode of action. The ContinueAt method of the ReadLine activity with name "r3" will be scheduled (the name of the WF program queue created by ReadLine is the same as its Name property). This will cause the ReadLine activity to complete, which will schedule notification of its Closed event to the enclosing Sequence "s2". That Sequence will schedule the execution of the WriteLine "w3" that follows the just-completed ReadLine. The WriteLine will get the string received by the ReadLine activity (via activity databinding) and write it to the console. The WriteLine will complete, again causing a notification to the enclosing Sequence. The Sequence will then move on to its third child activity, another ReadLine, which will now wait until an item is enqueued into its WF program queue.

The series of steps just described will result in the state of the program shown in Figure 3.20.

Figure 3.20

Figure 3.20 WF program instance in Listing 3.18 again in an idle state

This example is typical of the episodic execution we described at the outset of the chapter. As a result of stimulus from the external world, the WF program instance moves forward. And it is truly the composite activities that are driving the program's execution by providing the control flow; the WF runtime is passively dispatching whatever items appear in the scheduler work queue while enforcing adherence to the activity automaton.

Once you understand the activity automaton and the execution-related rules of activity composition, it is easy to model other control flow patterns beyond simple sequential and interleaved execution. This allows your programs to mirror more precisely whatever processes they are trying to coordinate. In the next chapter, we will look at several additional aspects of composite activity development that aid in building different kinds of control flow.

It may be helpful to pause here and consolidate what you've learned from this chapter so far. As an exercise, we suggest writing a custom composite activity. An appropriate choice on which to test your skills is PrioritizedInterleave. The PrioritizedInterleave activity executes its child activities in priority order. Each child activity has a property, named Priority, of type int.

When PrioritizedInterleave executes, first all child activities with a priority of 1 are executed in an interleaved manner; when those are completed, all child activities with a priority of 2 are executed (also in an interleaved manner). This continues until all child activities have been executed. As you might guess, the execution logic of PrioritizedInterleave is something of a combination of the logic we developed for Sequence and the logic we developed for Interleave.

Listing 3.19 shows an example WF program containing a Prioritized-Interleave. The seven child activities of the PrioritizedInterleave are grouped into three different sets according to the values of their Priority property. The best way to implement the Priority property is as an attached property, which supports the XAML syntax shown in Listing 3.19. Attached properties are covered in Chapter 7, "Advanced Authoring." You can take a simpler approach and add a Priority property to WriteLine and then test your PrioritizedParallel activity using the modified WriteLine.

Listing 3.19. WF Program that Is a PrioritizedInterleave

<PrioritizedInterleave xmlns="http://EssentialWF/Activities">
  <B PrioritizedInterleave.Priority="1" />
  <C PrioritizedInterleave.Priority="2" />
  <A PrioritizedInterleave.Priority="1" />
  <E PrioritizedInterleave.Priority="2" />
  <F PrioritizedInterleave.Priority="3" />
  <G PrioritizedInterleave.Priority="3" />
  <D PrioritizedInterleave.Priority="2" />

This WF program is depicted in a more readable form in Figure 3.21, which conveys the interleaved execution that occurs within the groupings of child activities.

Figure 3.21

Figure 3.21 PrioritizedInterleave activity containing three groupings

You might conclude from Figure 3.21 that this WF program could just as easily be built using the Sequence and Interleave activities we developed previously. True, but there is another way of looking at things. Both Sequence and Interleave are nothing but degenerate cases of our PrioritizedInterleave, in which the priorities of the child activities are either all different or all the same, respectively. This is a simple but instructive example of the control flow flexibility afforded by the composition model of WF.

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.


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.


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.


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.


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


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


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.


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.


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