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

This chapter is from the book

Bookmarks Revisited

The simplest activities (in terms of execution logic) are like the WriteLine activity; they complete their work entirely within their Execute method. If all activities did this, you would not be able to build very interesting WF programs. Don't get us wrong; simple activities are useful, and indeed are essential to the definition of most WF programs. Typical duties for such activities include obtaining services and exchanging data with those services, and manipulating the state of the WF program instance.

Most real-world processes, however, reach points in time at which further computational progress cannot be made without stimulus (input) from an external entity. It may be that a WF program waits for a person to make a decision about which branch of execution logic should be taken. Or it may be that an activity delegates some computation to an external entity and then waits for the result of that computation to be returned asynchronously.

In order to understand the mechanics of how this kind of activity executes, we will begin by looking at a contrived example: an activity that delegates work to...itself. Consider the version of WriteLine that is shown in Listing 3.6.

Listing 3.6. WriteLine Activity That Uses a Bookmark

using System;
using System.Workflow.ComponentModel;

namespace EssentialWF.Activities
{
  public class WriteLine : Activity
  {
    // Text property elided for clarity...

    protected override ActivityExecutionStatus Execute(
      ActivityExecutionContext context)
    {
      base.Invoke(this.ContinueAt, EventArgs.Empty);
      return ActivityExecutionStatus.Executing;
    }

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

      WriterService writer = context.GetService<WriterService>();
      writer.Write(Text);

      context.CloseActivity();
    }
  }
}

Although the example is contrived, there are several things worth looking at here.

By calling Invoke<T> (a protected method defined by Activity), the WriteLine activity creates a bookmark and immediately resumes that bookmark. The bookmark's resumption point is the WriteLine.ContinueAt method, and the payload for the resumed bookmark is EventArgs.Empty.

The bookmark created by the call to Invoke<T> is managed internally by the WF runtime, and because the Invoke<T> method also resumes this bookmark, an item is enqueued in the scheduler work queue (corresponding to the ContinueAt method).

Because it creates a bookmark (and is awaiting resumption of that bookmark), the WriteLine activity can no longer report its completion at the end of the Execute method. Instead it returns a value of ActivityExecutionStatus.Executing, indicating that although WriteLine is yielding the CLR thread by returning from Execute, its work is not complete since there is a pending bookmark. The WriteLine activity remains in the Executing state and does not transition (yet) to Closed.

When the scheduler dispatches the work item corresponding to the ContinueAt method, it passes an ActivityExecutionContext as the sender parameter. This allows the WriteLine to have access to its current execution context.

The ContinueAt method conforms to a standard .NET Framework event handler signature and therefore has a return type of void. Because of this, the WF runtime cannot use the return value of ContinueAt as the way of determining whether or not the activity should remain in the Executing state or transition to the Closed state. The CloseActivity method provided by ActivityExecutionContext can be used instead. If this method is called, the currently executing activity moves to the Closed state; if the method is not called, there is no change in the state of the activity. Because ContinueAt calls CloseActivity, the WriteLine activity moves to the Closed state.

The version of the WriteLine activity that uses Invoke<T>, though contrived, is still illustrative of the general pattern that you will need to use in many of the activities you develop. Although it is possible for an activity to complete its work within the Execute method (as with the version of WriteLine that returns ActivityExecutionStatus.Closed from its Execute method), this is a special case. Just as subroutines are a special, simple case accommodated by the richer concept of a coroutine, activities whose execution logic is embodied in a single Execute method are a special, simple form of episodic computation, in which there is always exactly one episode.

WF Program Execution

Now that we understand the basics of how to write activity execution logic, we can take a closer look at the execution mechanics of a WF program. We will start with a WF program that contains just one activity:

<WriteLine Text="hello, world" xmlns="http://EssentialWF/Activities" />

Running this program results in the expected output:

hello, world

In Chapter 2, "WF Programs," we briefly looked at the code that is required to host the WF runtime and run WF programs. We will return to the host-facing side of the WF runtime in Chapter 5, "Applications." For now, it is enough to know the basics: First, the WorkflowRuntime.CreateWorkflow method returns a WorkflowInstance representing a newly created instance of a WF program; second, the WorkflowInstance.Start method tells the WF runtime to begin the execution of that WF program instance.

The call to WorkflowRuntime.CreateWorkflow prepares a scheduler (and the accompanying scheduler work queue) for the new WF program instance. When this method returns, all activities in the WF program are in the Initialized state.

The call to WorkflowInstance.Start enqueues one item in the the scheduler work queue—a delegate corresponding to the Execute method of the root activity of the WF program. The root activity—in our example, the WriteLine—is now in the Executing state, even though the Execute method has not actually been called (the work item has not yet been dispatched). The scheduler work queue is shown in Figure 3.6.

Figure 3.6

Figure 3.6 Scheduler work queue after WorkflowInstance.Start

Let's assume that we are using the version of WriteLine that doesn't call Invoke<T>.

When the Execute method returns a value of ActivityExecutionStatus. Closed, the WriteLine activity moves to the Closed state. In this case, the WF runtime recognizes that the program instance is complete since the root activity in the program instance is complete.

The asynchronous version of WriteLine is only slightly more complex. The call to Invoke<T> within Execute will enqueue a work item in the scheduler work queue (corresponding to the resumption of the internally created bookmark).

Thus, when the Execute method (of the version of WriteLine that does call Invoke<T>) returns, the activity remains in the Executing state and the scheduler work queue looks as it is shown in Figure 3.7.

Figure 3.7

Figure 3.7 Scheduler work queue after WriteLine.Execute

When the WriteLine.ContinueAt method returns, the WriteLine activity moves to the Closed state and the program instance completes.

WF Program Queues

Any activity that requires input from an external entity must figure out a way to (a) let that external entity know that it requires input, and (b) receive notification when the input is available. This simple pattern is at the heart of episodic computation, and it is supported in a first-class way by the WF runtime. The plain requirement is that an activity must be able to receive input even if the WF program instance in which it exists is idle and sitting in persistent storage like a SQL Server database table. When the input arrives, the WF program instance must be reactivated and its execution resumed (at the appropriate bookmark).

In Chapter 2, we developed an activity called ReadLine (shown again in Listing 3.7), which waits for a string to arrive from an external entity. If you understand how this activity is built and how it executes, you will have the right basis for understanding and creating higher level communication patterns that are used in WF programs. All such patterns are built on top of the same notion of bookmarks.1

Listing 3.7. ReadLine Activity

using System;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;

namespace EssentialWF.Activities
{
  public class ReadLine : Activity
  {
    private string text;
    public string Text
    {
      get { return text; }
    }

    protected override ActivityExecutionStatus Execute(
      ActivityExecutionContext context)
    {
      WorkflowQueuingService qService =
        context.GetService<WorkflowQueuingService>();

      WorkflowQueue queue =
        qService.CreateWorkflowQueue(this.Name, true);
      queue.QueueItemAvailable += this.ContinueAt;

      return ActivityExecutionStatus.Executing;
    }

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

      WorkflowQueuingService qService =
        context.GetService<WorkflowQueuingService>();

      WorkflowQueue queue = qService.GetWorkflowQueue(this.Name);
      text = (string) queue.Dequeue();
      qService.DeleteWorkflowQueue(this.Name);

      context.CloseActivity();
    }
  }
}

The execution logic of the ReadLine activity uses a WF program queue. A WF program queue is essentially a named location (a bookmark) where an activity can receive data, even if the WF program instance in which the activity exists is not in memory. A WF program queue is not the same as the WF program instance's scheduler queue, which is managed by the WF runtime. Think of a WF program queue as the data structure in which an explicitly created bookmark holds its payload (to be delivered upon the resumption of the bookmark). It is an addressable location where external entities can deliver data.

The Execute method of ReadLine obtains the WorkflowQueuingService from its ActivityExecutionContext. The WorkflowQueuingService is asked to create a WF program queue with a name that is the same as that of the activity (this.Name). The name of a WF program queue can be any IComparable object; usually a string will suffice. We are choosing a simple queue naming convention here, but other schemes are possible. Regardless, the external code that provides input to a WF program instance must know the name of the appropriate WF program queue.

The WorkflowQueuingService type is shown in Listing 3.8.

Listing 3.8. WorkflowQueuingService

namespace System.Workflow.Runtime
{
  public class WorkflowQueuingService
  {
    public WorkflowQueue CreateWorkflowQueue(IComparable queueName,
      bool transactional);
    public bool Exists(IComparable queueName);
    public WorkflowQueue GetWorkflowQueue(IComparable queueName);
    public void DeleteWorkflowQueue(IComparable queueName);

    /* *** other members *** */
  }
}

The same WF program queue name may be used in more than one WF program instance. This just means that if we write a WF program containing a ReadLine activity named "r1", we can execute any number of instances of this WF program without any problems. Each instance will create a separate WF program queue with the name "r1". Because data is always enqueued to a specific WF program instance (via WorkflowInstance.EnqueueItem), there is no conflict or ambiguity. Another way of stating this is that WF program queues are not shared across WF program instances. This allows us to think of the logical address of a WF program queue as the WorkflowInstance.InstanceId identifying the WF program instance that owns the WF program queue, plus the WF program queue name.

A WF program queue acts as a conduit for communication between external entities and an activity in a WF program instance. Code outside of the WF program instance can deposit data into a WF program queue using the EnqueueItem method defined on the WorkflowInstance class. An activity (and, by extension, a WF program) can create as many distinct WF program queues as it requires.

The CreateWorkflowQueue method returns a WorkflowQueue object that represents the WF program queue. The WorkflowQueue type is shown in Listing 3.9.

Listing 3.9. WorkflowQueue

namespace System.Workflow.Runtime
{
  public class WorkflowQueue
  {
    public IComparable QueueName { get; }
    public int Count { get; }

    public object Dequeue();
    public object Peek();

    public event EventHandler<QueueEventArgs>
      QueueItemAvailable;

    /* *** other members *** */
  }
}

The QueueItemAvailable event is raised when an item is enqueued into the WF program queue. Under the covers, this is just a bookmark (disguised using C# event syntax).

The QueueItemAvailable event is also raised if, when an activity subscribes to this event, there are already (previously enqueued) items present in the WF program queue. This permits a decoupling of the delivery of data to a bookmark and the resumption of that bookmark.

Here is a simple WF program that contains only a single ReadLine activity:

<ReadLine x:Name="r1" xmlns="http://EssentialWF/Activities"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />

If we save this WF program as a file called "Read.xoml", we can execute it using the console application of Listing 3.10, which hosts the WF runtime and delivers data to the ReadLine activity via the WF program queue.

Listing 3.10. A Console Application That Delivers Data to a ReadLine Activity

using System;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Runtime;
using System.Xml;

class Program
{
  static void Main()
  {
    using (WorkflowRuntime runtime = new WorkflowRuntime())
    {
      TypeProvider tp = new TypeProvider(null);
      tp.AddAssemblyReference("EssentialWF.dll");
      runtime.AddService(tp);

      runtime.StartRuntime();

      runtime.WorkflowIdled += delegate(object sender,
        WorkflowEventArgs e)
      {
        Console.WriteLine("WF program instance " +
          e.WorkflowInstance.InstanceId + " is idle");
      };

      runtime.WorkflowCompleted += delegate(object sender,
        WorkflowCompletedEventArgs e)
      {
        Console.WriteLine("WF program instance " +
          e.WorkflowInstance.InstanceId + " completed");
      };

      WorkflowInstance instance = null;
      using (XmlTextReader reader = new XmlTextReader("Read.xoml"))
      {
        instance = runtime.CreateWorkflow(reader);
        instance.Start();
      }

      string text = Console.ReadLine();
      instance.EnqueueItem("r1", text, null, null);

      // Prevent Main from exiting before
      // the WF program instance completes
      Console.ReadLine();

      runtime.StopRuntime();
    }
  }
}

The console application calls WorkflowRuntime.CreateWorkflow, which loads the WF program from XAML. It then calls WorkflowInstance.Start, which causes the Execute method of ReadLine—the root activity in the WF program—to be scheduled.

The console application then waits for the user to enter text at the console. Meanwhile, the WF runtime begins the execution of the WF program instance on a thread that is different than the thread on which Main is running. The ReadLine activity has its Execute method invoked. The ReadLine activity creates its WF program queue and then waits for data to arrive there. Because there are no other items in the scheduler work queue, the WF program instance is idle.

The console application subscribes for the WorkflowRuntime.WorkflowIdled event and, when this event is raised by the WF runtime, writes the InstanceId of the WF program instance to the console:

WF program instance 631855e5-1958-4ce7-a29a-dc6f8e2a9238 is idle

When a line of text is read, the console application calls EnqueueItem, passing the text it received from the console as payload associated with the resumption of the bookmark.

The implementation of WorkflowInstance.EnqueueItem enqueues (in the scheduler work queue) work items for all activities that are subscribed to this WF program queue's QueueItemAvailable event. This is depicted in Figure 3.8.

Figure 3.8

Figure 3.8 Enqueuing data to a WF program queue

In our example, the ReadLine activity's callback is called ContinueAt. This delegate will be scheduled as a work item and dispatched by the scheduler; if the idle WF program instance had been passivated (not shown in this example), the WF runtime would automatically bring it back into memory.

The ReadLine activity will set its Text property with the string obtained from the Dequeue operation on its WF program queue. In the example, we are doing no error checking to ensure that the object is indeed of type string. The ContinueAt method informs the WF runtime that it is complete by calling CloseActivity. The WF program instance, because it only contains the ReadLine, also completes. The console application, which subscribed to the WorkflowRuntime.WorkflowCompleted event, prints this fact to the console.

WF Program instance 631855e5-1958-4ce7-a29a-dc6f8e2a9238 completed

If the console application tries to enqueue data to a WF program queue that does not exist, the EnqueueItem method will throw an InvalidOperationException indicating that the WF program queue could not be found. In our implementation of ReadLine, the WF program queue is not created until the ReadLine activity begins executing. Thus, the following lines of code are problematic:

WorkflowInstance instance = runtime.CreateWorkflow(...);
instance.EnqueueItem("r1", "hello", null, null);

The preceding code omits the call to WorkflowInstance.Start, and because of this the WF program queue named "r1" does not yet exist. In other words, the implementation of ReadLine requires that the application doesn't enqueue the data until after the ReadLine activity starts to execute. Even the code in the console application of Listing 3.9 presents a race condition because the execution of the WF program instance occurs on a different thread than the execution of the console application. We may be able to work around this race condition quite easily in our contrived example where the WF program is just a single ReadLine activity. But in a larger WF program, with many activities managing WF program queues, and executing at different times, this is a lot trickier.

One of the ways to mitigate this problem is to allow activities to create WF program queues during the creation of a WF program instance. This will ensure that, after the call to WorkflowRuntime.CreateWorkflow, the WF program instance can immediately receive data (even if it cannot yet process it, which will only begin once WorkflowInstance.Start is called). In a later section, we will change ReadLine to do exactly this.

Timers

Another example of an activity that cannot complete its execution logic entirely within the Execute method is a Wait activity that simply waits for a specified amount of time to elapse before completing. The Wait activity is shown in Listing 3.11.

Listing 3.11. Wait Activity

using System;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;

namespace EssentialWF.Activities
{
  public class Wait : Activity
  {
    private Guid timerId;

    public static readonly DependencyProperty DurationProperty
      = DependencyProperty.Register("Duration",
        typeof(TimeSpan), typeof(Wait));

    public TimeSpan Duration
    {
      get { return (TimeSpan) GetValue(DurationProperty); }
      set { SetValue(DurationProperty, value); }
    }

    protected override ActivityExecutionStatus Execute(
      ActivityExecutionContext context)
    {
      WorkflowQueuingService qService =
        context.GetService<WorkflowQueuingService>();

      timerId = Guid.NewGuid();

      WorkflowQueue queue = qService.CreateWorkflowQueue(
        timerId, true);
      queue.QueueItemAvailable += this.ContinueAt;

      TimerService timerService = context.GetService<TimerService>();
      timerService.SetTimer(timerId, Duration);

      return ActivityExecutionStatus.Executing;
    }

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

      WorkflowQueuingService qService =
        context.GetService<WorkflowQueuingService>();

      WorkflowQueue queue = qService.GetWorkflowQueue(timerId);
      qService.DeleteWorkflowQueue(timerId);

      context.CloseActivity();
    }
  }
}

Listing 3.11 shows the basic implementation of a Wait activity that depends upon an implementation of a TimerService (see Listing 3.12) for the actual management of the timer. The Wait activity, in its Execute method, creates a WF program queue providing the bookmark resumption point (ContinueAt) and calls TimerService.SetTimer, passing a unique identifier representing the timer. The TimerService is responsible for managing the actual timers. When the timer is triggered, the timer service resumes the bookmark by enqueuing data in the WF program queue created by the Wait activity. When the ContinueAt method is invoked by the scheduler (with the AEC as the sender argument), the Wait activity deletes the WF program queue and transitions to the Closed state.

The TimerService defines a SetTimer method that allows the activity to specify the duration of the timer as a TimeSpan, along with the name of the WF program queue that the TimerService will use to deliver a notification using WorkflowInstance.EnqueueItem (with a null payload) when the specified amount of time has elapsed.

Listing 3.12. TimerService Used by the Wait Activity

using System;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;

namespace EssentialWF.Activities
{
  public abstract class TimerService
  {
   public abstract void SetTimer(Guid timerId, TimeSpan duration);
   public abstract void CancelTimer(Guid timerId);
  }
}

A simple implementation of the timer service is shown in Listing 3.13.

Listing 3.13. Implementation of a TimerService

using System;
using System.Collections.Generic;
using System.Threading;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using EssentialWF.Activities;

namespace EssentialWF.Services
{
  public class SimpleTimerService : TimerService
  {
    WorkflowRuntime runtime;
    Dictionary<Guid, Timer> timers = new Dictionary<Guid, Timer>();

    public SimpleTimerService(WorkflowRuntime runtime)
    {
      this.runtime = runtime;
    }

    public override void SetTimer(Guid timerId, TimeSpan duration)
    {
      Guid instanceId = WorkflowEnvironment.WorkflowInstanceId;
      Timer timer = new Timer(delegate(object o)
        {
          WorkflowInstance instance = runtime.GetWorkflow(instanceId);
          instance.EnqueueItem(timerId, null, null, null);
        }, timerId, duration, new TimeSpan(Timeout.Infinite));

      timers.Add(timerId, timer);
    }

    public override void CancelTimer(Guid timerId)
    {
      ((IDisposable)timers[timerId]).Dispose();
      timers.Remove(timerId);
    }
  }
}

The SimpleTimerService mantains a set of System.Threading.Timer objects. The timerId that is passed as a parameter to the SetTimer method serves as the name of the WF program queue created by the Wait activity. When a timer fires, the callback (written as an anonymous method) enqueues a (null) item into the appropriate WF program queue, and the Wait activity resumes its execution.

In Chapter 6 we will discuss transactions, and we will see how transactional services (such as a durable timer service) can be implemented. Because we have followed the practice of making the Wait activity dependent only on the abstract definition of the timer service, we can change the implementation of the timer service without affecting our activities and WF programs.

As mentioned earlier, the WF runtime is a container of services. Custom services that are added to the WF runtime can be obtained by executing activities. An implementation of a TimerService can be added to the WF runtime like so:

using (WorkflowRuntime runtime = new WorkflowRuntime())
{
  runtime.AddService(new SimpleTimerService(runtime));
  ...
}

Executing the Wait activity within a simple WF program will cause the program to pause (and potentially passivate) and later, when the timeout occurs, resume the execution. The following program will start and then pause for 5 seconds, and finally resume its execution and complete:

<Wait Duration="00:00:05" xmlns="http://EssentialWF/Activities" />

Our reason for introducing the Wait activity is to illustrate a general pattern.

There is nothing at all special about timers. The Wait activity makes a request to a service on which it depends, and indicates to the service where (to which WF program queue) the result of the requested work should be delivered. The service takes some amount of time to complete the requested work. When the work is done, the service returns the result of the work to the activity via the WF program queue.

This bookmarking pattern is the basis for developing WF programs that are "coordinators of work" that is performed outside their boundaries.

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