Home > Articles > Programming > Visual Basic

This chapter is from the book

This chapter is from the book

Getting Started with Async/Await

In this section you see the Async pattern with an example based on retrieving information from the Internet. You will build a WPF application that downloads RSS feeds information from the Web, simulating a long-running process over a network. You first, though, create an application that works synchronously; then you see how to implement the Event-based Asynchronous Pattern described at the beginning of this chapter. Finally, you learn how things change in a third example built using the new Async and Await keywords.

The Synchronous Approach

Create a new WPF project with Visual Basic 2015 and .NET Framework 4.6. The application will consume the Visual Basic RSS feed exposed by the Microsoft’s Channel9 website, with particular regard to the list of published videos. Each item in the feed has a large number of properties, but for the sake of simplicity only the most important will be presented in the application’s UI. So, the first thing you need to do is create a class that represents a single video described in the feed. Listing 42.1 demonstrates how to implement a class called Video.

LISTING 42.1 Representing a Single Video

Public Class Video
    Public Property Title As String
    Public Property Url As String
    Public Property Thumbnail As String
    Public Property DateRecorded As String
    Public Property Speaker As String

    Public Shared FeedUrl As String = _
           "http://channel9.msdn.com/Tags/visual+basic/RSS"
End Class

Notice that all the properties are of type String just to represent values as they exactly come from the feed. Also, a shared field contains the feed URL. Now open the MainWindow.xaml file, to prepare the application’s user interface. The goal is to show the videos’ thumbnails and summary information and to provide the ability to click a thumbnail to open the video in its original location. The ListBox control is a good choice to display a collection of items. This will be placed inside the default Grid panel. Each item in the ListBox will be presented via a custom template made of a Border and a StackPanel that contains an Image control (for the video thumbnail) and a number of TextBlock controls that are bound to properties of the Video class. Listing 42.2 shows the full code for the main window.

LISTING 42.2 Implementing the Application’s User Interface

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox Name="VideoBox" ItemsSource="{Binding}"
                 ScrollViewer.HorizontalScrollBarVisibility="Disabled">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel VirtualizingPanel.IsVirtualizing="True"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="Black" Margin="5"
                            BorderThickness="2" Tag={Binding Url}
                            MouseLeftButtonUp="Border_MouseLeftButtonUp_1"
                            Width="200" Height="220">
                        <StackPanel>
                            <Image Source="{Binding Thumbnail}"
                                   Width="160" Height="120" />
                            <TextBlock Text="{Binding Title}" TextWrapping="Wrap"
                                       Grid.Row="1"/>
                            <TextBlock Text="{Binding DateRecorded}" Grid.Row="2"/>
                            <TextBlock Text="{Binding Speaker}" Grid.Row="3"/>
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

It is worth mentioning that the code replaces the default items container (a VirtualizingStackPanel) with a WrapPanel container so that items are not forced to be presented on one line horizontally. This requires disabling the horizontal scrollbar on the ListBox (ScrollViewer.HorizontalScrollBarVisibility="Disabled") and changing the ListBox.ItemsPanel content with the WrapPanel. Also notice how the Border.Tag property is bound to the Url property of the Video class. This enables you to store the video’s URL and click the Border at runtime to open the video in its original location. Now switch to the code-behind file. The first thing you must do is add a number of Imports directives, some for importing XML namespaces needed to map information from the RSS feed and some for working with additional .NET classes:

Imports System.Net
Imports <xmlns:media="http://search.yahoo.com/mrss/">
Imports <xmlns:dc="http://purl.org/dc/elements/1.1/">

The next step is implementing a method that queries the RSS feed returning the list of videos. In this first implementation, you will use a synchronous approach, which will block the user interface when the application is running:

Private Function QueryVideos() As IEnumerable(Of Video)
    Dim client As New WebClient

    Dim data As String = client.DownloadString(New Uri(Video.FeedUrl))

    Dim doc As XDocument = XDocument.Parse(data)
        Dim query = From video In doc...<item>
                    Select New Video With {
                        .Title = video.<title>.Value,
                        .Speaker = video.<dc:creator>.Value,
                        .Url = video.<link>.Value,
                        .Thumbnail = video...<media:thumbnail>.
                        FirstOrDefault?.@url,
                        .DateRecorded = String.Concat("Recorded on ",
                         Date.Parse(video.<pubDate>.Value,
                         Globalization.CultureInfo.InvariantCulture).
                         ToShortDateString)}
    Return query
End Function

The code is simple. An instance of the WebClient class, which provides simplified access to networked resources, is created and the invocation of its DownloadString method downloads the entire content of the feed under the form of a String object. Notice that this is the point at which the user interface gets blocked. In fact, it will need to wait for DownloadString to complete the operation before returning to be responsive. After the feed has been downloaded, it is converted into an object of type XDocument and a LINQ query enables you to retrieve all the needed information (refer to Chapter 27, “Manipulating XML Documents with LINQ and XML Literals,” for further information on LINQ to XML). Finally, a method called LoadVideos will run the query and assign the result to the Window’s DataContext; such a method will be invoked at startup. You can change this type of implementation, but it will be more useful later when making comparisons with the asynchronous implementation. The following code demonstrates this, plus the event handler for the MouseLeftButtonUp event of the Border control, where you launch the video in its original web page:

Private Sub LoadVideos()
    Me.DataContext = QueryVideos()
End Sub

Private Sub MainWindow_Loaded(sender As Object,
                             e As RoutedEventArgs) Handles Me.Loaded
    LoadVideos()
End Sub

Private Sub Border_MouseLeftButtonUp_1(sender As Object,
                                       e As MouseButtonEventArgs)
    'Tag is of type Object so an explicit conversion to String is required
    Dim instance = CType(sender, Border)
    Process.Start(CStr(instance.Tag))
End Sub

You can now run the application. Figure 42.3 shows the result of the query over the video feed.

FIGURE 42.3

FIGURE 42.3 Loading an RSS feed the synchronous way.

The application works as expected, but the real problem with this approach is that the user cannot interact with the interface while the query is running. The reason is that the query’s code is running in the UI thread, so the user interface is busy with the query and does not accept any interaction. This can be easily demonstrated by attempting to move the window while the query is running because you will not be able to move it elsewhere. This has other implications: you cannot refresh controls that display the status of the task because they would be refreshed only when the query completes. Also, you cannot enable users to cancel the operation because you would need a button that the user would never be able to click.

Event-Based Asynchrony and Callbacks

A much better approach is moving the long-running operation into a separate thread, so that the UI can remain responsive while the other thread executes the operation. Lots of classes in the .NET Framework, especially those whose job is interacting with the Web and with networks-expose event-based asynchrony through methods that launch and execute an operation on a separate thread and raise an event when it is completed, passing the result to the caller via a callback. The WebClient class has an asynchronous counterpart of DownloadString, called DownloadStringAsync, that you can use to execute the code on a separate thread and wait for the query result via a callback. The following code demonstrates how to accomplish this (do not worry if you notice something wrong because an explanation is provided in moments):

Private Function QueryVideos() As IEnumerable(Of Video)
    Dim client As New WebClient
    Dim query As IEnumerable(Of Video)

    AddHandler client.DownloadStringCompleted, Sub(sender, e)
                                                   If e.Error IsNot Nothing Then
                                                       'Error handling logic here..
                                                   End If

                                                   Dim doc = _
                                                       XDocument.Parse(e.Result)
                                                   Dim query = From video
                                                               In doc...<item>
                                                               Select _
                                                               New Video With {
                                                     .Title =
                                                     video.<title>.Value,
                                                     .Speaker =
                                                     video.<dc:creator>.
                                                     Value,
                                                     .Url = video.<link>.Value,
                                                     .Thumbnail =
                                                     video...<media:thumbnail>.
                                                     FirstOrDefault?.@url,
                                                     .DateRecorded =
                                                     String.Concat("Recorded on ",
                                                     Date.Parse(video.
                                                     <pubDate>.Value,
                                                     Globalization.CultureInfo.
                                                    InvariantCulture).
                                                    ToShortDateString)}
                                               End Sub

    client.DownloadStringAsync(New Uri(Video.FeedUrl))
    Return query
End Function

The code specifies a statement lambda as an event handler for the DownloadString-Completed event, instead of declaring a separate delegate and pointing to this via an AddressOf clause. The e object is of type DownloadStringCompletedEventArgs and contains the result of the operation. The problem in this code is that the Return statement does not work because it is attempting to return a result that has not been produced yet. On the other side, you cannot write something like this:

Private Function QueryVideos() As IEnumerable(Of Video)
    Dim client As New WebClient
    Dim query As IEnumerable(Of Video)

    AddHandler client.DownloadStringCompleted, Sub(sender, e)
                                                   If e.Error IsNot Nothing Then
                                                       'Error handling logic here..
                                                   End If

                                                   Dim doc = _
                                                       XDocument.Parse(e.Result)
                                                   Dim query = From ...
                                                   Return query

                                               End Sub

    client.DownloadStringAsync(New Uri(Video.FeedUrl))
End Function

This code does not work because you cannot return a result from a Sub and because it should be returned from the outer method, not the inner. In conclusion, Return statements do not work well with event-based asynchrony. The solution at this point is returning the result via a callback and an Action(Of T) object. So the appropriate implementation of the QueryVideos method in this approach is the following:

Private Sub QueryVideos(listOfVideos As Action(Of IEnumerable(Of Video),
                                        Exception))
    Dim client As New WebClient
    AddHandler client.DownloadStringCompleted, Sub(sender, e)
                                                   If e.Error IsNot Nothing Then
                                                       listOfVideos(Nothing,
                                                                    e.Error)
                                                       Return
                                                   End If

                                                   Dim doc = _
                                                       XDocument.Parse(e.Result)
                                                   Dim query =
                                                       From video In doc...<item>
                                                       Select New Video With {
                                                       .Title =
                                                       video.<title>.Value,
                                                       .Speaker =
                                                       video.<dc:creator>.
                                                       Value,
                                                       .Url = video.<link>.Value,
                                                       .Thumbnail =
                                                       video...<media:thumbnail>.
                                                       FirstOrDefault?.@url,
                                                       .DateRecorded =
                                                       String.Concat("Recorded on ",
                                                       Date.Parse(video.
                                                       <pubDate>.Value,
                                                       Globalization.CultureInfo.
                                                       InvariantCulture).
                                                       ToShortDateString)}
                                                   listOfVideos(query, Nothing)
                                               End Sub

    Try
        client.DownloadStringAsync(New Uri(Video.FeedUrl))
    Catch ex As Exception
        listOfVideos(Nothing, ex)
    End Try
End Sub

The previous code does the following:

  1. Holds the list of videos from the RSS feed in an Action(Of IEnumerable(Of Video), Exception) object. The Exception instance here is useful to determine whether an error occurred during the query execution.
  2. If the query completes successfully, the query result is passed to the Action object (that is, the callback).
  3. If an exception occurs during the query execution (see the first If block inside the statement lambda), the callback receives Nothing as the first parameter because the collection of items was not retrieved successfully and the exception instance as the second parameter.
  4. If an exception occurs immediately when the web request is made, the callback still receives Nothing and the exception instance. This is at the Try..Catch block level.

So using a callback here has been necessary for two reasons: sending the query result back to the caller correctly and handling two exception scenarios. But you are not done yet. In fact, you have to completely rewrite the LoadVideos method to hold the result of the callback and determine whether the operation completed successfully before assigning the query result to the Window’s DataContext. The following code demonstrates this:

Private Sub LoadVideos()
    Dim action As Action(Of IEnumerable(Of Video),
                         Exception) = Nothing
    action =
        Sub(videos, ex)
            If ex IsNot Nothing Then
                MessageBox.Show(ex.Message)
                Return
            End If

            If (videos.Any) Then
                Me.DataContext = videos
            Else
                QueryVideos(action)
            End If
        End Sub
    QueryVideos(action)
End Sub

As you can see, the code is not easy, unless you are an expert. There is an invocation to the previous implementation of QueryVideos, passing the instance of the callback. When the result is sent back, the statement lambda first checks for exceptions and, if not, takes the query result as the data source. If you now run the application again, you will get the same result shown in Figure 42.3; however, this time the user interface is responsive and the user can interact with it. But reaching this objective had costs. You had to completely rewrite method implementations and write code that is complex and difficult to read and to extend. So, the multithreading in this situation has not been very helpful. This is the point in which the Async/Await pattern comes in to make things simple.

Asynchrony with Async/Await

The Async/Await pattern has the goal of simplifying the way developers write asynchronous code. You will learn a lot about the underlying infrastructure, but before digging into that, it is important for you to see how your code can be much cleaner and readable. Let’s start by modifying the QueryVideos method to make some important considerations:

Private Async Function QueryVideosAsync() As  _
        Task(Of IEnumerable(Of Video))
    Dim client As New WebClient

    Dim data = Await client.DownloadStringTaskAsync(New Uri(Video.FeedUrl))

    Dim doc = XDocument.Parse(data)

    Dim query = From video In doc...<item>
                Select New Video With {
                    .Title = video.<title>.Value,
                    .Speaker = video.<dc:creator>.Value,
                    .Url = video.<link>.Value,
                    .Thumbnail = video...<media:thumbnail>.
                    FirstOrDefault?.@url,
                    .DateRecorded = String.Concat("Recorded on ",
                    Date.Parse(video.<pubDate>.Value,
                        Globalization.CultureInfo.InvariantCulture).
                    ToShortDateString)}

    Return query
End Function

Asynchronous methods must be decorated with the Async modifier. When the compiler encounters this modifier, it expects that the method body contains one or more Await statements. If not, it reports a warning saying that the method will be treated as synchronous, suggesting that the Async modifier should be removed. Async methods must return an object of type Task. If the method returns a value (Function), then it must return a Task(Of T) where T is the actual result type. Otherwise, if the method returns no value, both following syntaxes are allowed:

Async Function TestAsync() As Task
   'You can avoid Return statements, the compiler assumes returning no values
End Function

Async Sub TestAsync()
'...
End Sub

The difference between the two implementations is that the first one can be called inside another method with Await, but the second one cannot (because it does not need to be awaited). A typical example of the second syntax is about event handlers: they can be asynchronous and can use Await, but no other method will wait for their result. By convention, the suffix of asynchronous methods is the Async literal. This is why QueryVideos has been renamed into QueryVideosAsync. An exception is represented by asynchronous methods already existing in previous versions of the .NET Framework, based on the EAP, whose name already ends with Async. In this case Async is replaced with TaskAsync. For instance (as you discover in moments), the DownloadStringAsync method in the WebClient class has a new counterpart called DownloadStringTaskAsync. Any method that returns a Task or Task(Of T) can be used with Await. With Await, a task is started but the control flow is immediately returned to the caller. The result of the task will not be returned immediately, but later and only when the task completes. But because the control flow immediately returns to the caller, the caller remains responsive. Await can be thought as of a placeholder for the task’s result, which will be available after the awaited task completes. In the previous QueryVideosAsync method, Await starts the WebClient.DownloadStringTaskAsync method and literally waits for its result but, while waiting, the control flow does not move to DownloadStringAsyncTask, while it remains in the caller. Because in the current example the code is running in the UI thread, the user interface remains responsive because the requested task is being executed asynchronously.

In other words, what Await actually does is sign up the rest of the method as a callback on the task, returning immediately. When the task that is being awaited completes, it will invoke the callback and will resume the execution from the exact point it was left.

After the operation has been completed, the rest of the code can elaborate the result. With this kind of approach, your method looks much simpler, like the first synchronous version, but it is running asynchronously with only three edits (the Async modifier, Task(Of T) as the return type, and the Await operator).

Continuing considerations on the previous method, take a look at the final Return statement. It is returning an IEnumerable(Of Video), but actually the method’s signature requires returning a Task(Of IEnumerable(Of Video)). This is possible because the compiler automatically makes Return statements to return a Task-based version of their result even if they do not. As a result, you will not get confused because you will write the same code but the compiler will take care of converting the return type into the appropriate type. This also makes migration of synchronous code to asynchronous easier. Technically speaking, the compiler synthesizes a new Task(Of T) object at the first Await in the method. This Task(Of T) object is returned to the caller at the first Await. Later on, when it encounters a Return statement, the compiler causes that already-existing Task(Of T) object to transition from a “not yet completed” state into the “completed with result” state. Continuing the migration of the code example to the Async/Await pattern, you now need a few edits to the LoadVideos method. The following code demonstrates this:

Private Async Function LoadVideosAsync() As Task
    Me.DataContext = Await QueryVideosAsync()
End Sub

The method is now called LoadVideoAsync and marked with the Async modifier. The reason is that it contains an Await expression that invokes the QueryVideosAsync method. The result of this invocation is taken as the main window’s data source. Finally, you have to edit the MainWindow_Loaded event handler and make it asynchronous like this:

Private Async Sub MainWindow_Loaded(sender As Object,
                                    e As RoutedEventArgs) Handles Me.Loaded
    Await LoadVideosAsync()
End Sub

If you now run the application, you will still get a responsive user interface that you can interact with while the long-running task (the query) is executing, but you have achieved this by modifying existing code with very few edits.

How Async and Await Work Behind the Scenes

Behind the scenes of the ease of the Async pattern, the compiler does incredible work to make the magic possible. When you make an asynchronous call by using Await, that invocation starts a new instance of the Task class. As you know from Chapter 41, one thread can contain multiple Task instances. So you might have the asynchronous operation running in the same thread but on a new Task. Internally, it’s as if the compiler could split an asynchronous method in two parts, a method and its callback. If you consider the QueryVideosAsync shown previously, you could imagine a method defined until the invocation of Await. The next part of the method is moved into a callback that is invoked after the awaited operation is completed. This has two benefits. The first benefit is that it ensures that code that needs to manipulate the result of an awaited operation will work with the actual result, which has been returned after completion of the task (this is in fact the moment in which the callback is invoked). Second, such callback is invoked in the same calling thread, which avoids the need of managing threads manually or using the Dispatcher class in technologies like WPF or Silverlight. Figure 42.4 gives you an ideal representation of how the asynchronous method has been split.

FIGURE 42.4

FIGURE 42.4 An asynchronous method is split into two ideal parts; the second is a callback.

Beyond considerations like the ones about threading, it is interesting to analyze the code the compiler generated to make asynchrony so efficient in Visual Basic 2015. For this exercise, you need a decompiler tool such as .NET Reflector from Red-Gate, which is available as a free trial from https://www.red-gate.com/products/dotnet-development/reflector/. If you open the compiled .exe file with a tool like this, you can see that the implementation of asynchronous methods is completely different from the one you wrote and that the compiler generated several structures that implement a state machine that supports asynchrony. Figure 42.5 shows the QueryVideosAsync real implementation.

FIGURE 42.5

FIGURE 42.5 Investigating the actual generated code with .NET Reflector.

For your convenience, the following is the auto-generated code for QueryVideosAsync:

<AsyncStateMachine(GetType(VB$StateMachine_1_QueryVideosAsync))> _
Private Function QueryVideosAsync() As Task(Of IEnumerable(Of Video))
    Dim stateMachine As New VB$StateMachine_1_QueryVideosAsync With { _
        .$VB$Me = Me, _
        .$State = -1, _
        .$Builder = AsyncTaskMethodBuilder(Of IEnumerable(Of Video)).Create _
    }
    stateMachine.$Builder.
    Start(Of VB$StateMachine_1_QueryVideosAsync)(stateMachine)
    Return stateMachine.$Builder.Task
End Function

You do not need to know the code in detail because this implementation is purely internal; however, the real code relies on the AsyncTaskMethodBuilder class, which creates an instance of an asynchronous method and requires specifying a state machine that controls the execution of the asynchronous task (which is returned once completed). For each asynchronous method, the compiler generated an object representing the state machine. For instance, the compiler generated an object called VB$StateMachine_1_QueryVideosAsync that represents the state machine that controls the execution of the QueryVideosAsync method. Listing 42.3 contains the code of the aforementioned structure.

LISTING 42.3 Internal Implementation of a State Machine for Async Methods

<CompilerGenerated> _
Private NotInheritable Class VB$StateMachine_1_QueryVideosAsync
    Implements IAsyncStateMachine
    ' Methods
    Public Sub New()
    <CompilerGenerated> _
    Friend Sub MoveNext() Implements IAsyncStateMachine.MoveNext
    <DebuggerNonUserCode> _
    Private Sub SetStateMachine(stateMachine As IAsyncStateMachine) _
            Implements IAsyncStateMachine.SetStateMachine

    ' Fields
    Friend $A0 As TaskAwaiter(Of String)
    Public $Builder As AsyncTaskMethodBuilder(Of IEnumerable(Of Video))
    Public $State As Integer
    Friend $VB$Me As MainWindow
    Friend $VB$ResumableLocal_client$0 As WebClient
    Friend $VB$ResumableLocal_data$1 As String
    Friend $VB$ResumableLocal_doc$2 As XDocument
    Friend $VB$ResumableLocal_query$3 As IEnumerable(Of Video)
End Class

The code in Listing 42.3 is certainly complex, and you are not required to know how it works under the hood, but focus for a moment on the MoveNext method. This method is responsible of the asynchronous execution of tasks; depending on the state of the task, it resumes the execution at the appropriate point. You can see how the compiler translates the Await keyword into an instance of the TaskAwaiter structure, which is assigned with the result of the invocation to the Task.GetAwaiter method (both are for compiler-use only). If you compare the result of this analysis with the ease of usage of the Async pattern, it is obvious that the compiler does tremendous work to translate that simplicity into a very efficient asynchronous mechanism.

Documentation and Examples of the Async Pattern

Microsoft offers a lot of useful resources to developers who want to start coding the new way. The following list summarizes several resources that you are strongly encouraged to visit to make your learning of Async complete:

Do not leave out of your bookmarks the root page of the .NET Framework 4.5 and 4.6 documentation (http://msdn.microsoft.com/en-us/library/w0x726c2).

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