Opting out of Google Instant Browsing

I recently wrote about a new feature of Google Chrome called Instant Browsing. You can turn it on or off Basic tab of the  Options page in Chrome.

If you are a web site owner/administrator and are concerned about the impact it might have on your web server to have a deluge of requests going to your server that the end user is probably not really interested in, or having a number of requests going to your server that result in a 404 resource not found because the half formed URL in the “omnibox” does not actually resolve to a real page then you can opt out.

For folks running ASP.NET (both WebForms and MVC) I’ve created a simple HTTP Module that will opt your site out if it encounters requests from Chromes’ Instant Browsing.

You can download the Module here: Instant Browsing HTTP Module V1. And to activate it in your application you need to add the DLL file as a reference to your application and then add the bolded line to your web.config.

<configuration>
 <system.web>
  <httpModules>
   <add name="InstantBrowsingOptOut" type="InstantBrowsing.InstantBrowsingOptOut, InstantBrowsing"/>
  </httpModules>
 </system.web>
</configuration>

 

The Code

If you prefer, you can add the following source to your application and compile it yourself.

using System;
using System.Collections.Specialized;
using System.Web;

namespace InstantBrowsing
{
    public class InstantBrowsingOptOut : IHttpModule
    {
        public void Dispose()
        {
            // Nothing to dispose. Required by IHttpModule
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(BeginRequest);
        }

        void BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;

            string headerValue = GetPurposeHeaderValue(application.Request);
            if (HeaderValueDoesntExist(headerValue))
                return;

            if (PreviewMode(headerValue))
                Issue403Forbidden(application.Response);
        }

        private bool PreviewMode(string value)
        {
            return value.ToLowerInvariant().Contains("preview");
        }

        private bool HeaderValueDoesntExist(string value)
        {
            return string.IsNullOrEmpty(value);
        }

        private string GetPurposeHeaderValue(HttpRequest request)
        {
            NameValueCollection headers = request.Headers;
            return headers["X-Purpose"];
        }

        private void Issue403Forbidden(HttpResponse response)
        {
            response.Clear();
            response.StatusCode = 403;
            response.End();
        }
    }
}

And to activate it in your application you need to add the bolded line to your web.config.

<configuration>
 <system.web>
  <httpModules>
   <add name="InstantBrowsingOptOut" type="InstantBrowsing.InstantBrowsingOptOut, InstantBrowsing"/>
  </httpModules>
 </system.web>
</configuration>

Note that the second “InstantBrowsing” in the type attribute is the assembly, so if you’ve put it in an assembly with a different name you’ll need to change the type attribute to reflect that.

Google Instant Browsing

What is Instant Browsing?

Google Chrome 12 comes with a some features, including support for Google’s Instant Searching and Instant Browsing via the address bar (or “omnibar” as they call it). This means that as you type your query or URL it will be sent off with almost every character press constantly updating the page underneath.

If you have this version of Chrome and don’t currently see what I’m talking about you can go into the Options and in the Basic tab look for the Search options. Make sure that “Enable Instant for faster searching and browsing” is turned on.

Now you will see what happens as you type URLs into the “omnibox” (the address bar). If you additionally run Fiddler you’ll see how many requests are being made in the background.

For example, if I start typing my blog URL, by the time I’ve finished typing my forename it has already concluded that I want to see my blog and I can see in fiddler it has already made the request to http://colinmackay.co.uk/ and my blog appears while I’m still typing.

What’s going on?

If I continue on, say I’m looking for something on SQL, I can see this progression in Fiddler of all the requests that get sent to my blog. (I’ve removed some of the other requests that are unimportant for this example)

As you can see, sometimes I can type quite quickly and it has to play catch up. Sometimes, I slow enough that the blog responds with a 301 (the server does its best to guess what you want, treating an invalid URL as a sort of search term and redirecting you to its best guess) or a 404 if it can’t resolve the URL.

Try this on the BBC News website – As you type URLs you get tons of 404 pages back as the intermediate (non-functioning urls) get responded to!

As you can see from the image of Fiddler above there are some requests missing, some were the browser pulling down CSS and images from my blog, others were request off to Google looking to augment the instant browsing feature.

These calls to augment the Instant Browsing feature are all being sent off to clients1.google.co.uk (I suspect that in each locale there will be a different set of URLs that are able to best match queries in that area). It consists of a GET request with the query in it. The query being what you have typed in the “omnibox”. For example: http://clients1.google.co.uk/complete/search?client=chrome&hl=en-GB&q=colinmackay.co.uk%2Fblog%2Ftasks

This results in some JSON being returned. If you are typing URLs it doesn’t appear to be that useful. The above returned the following to me:

["colinmackay.co.uk/blog/tasks",[],[],[],{"google:suggesttype":[]}]

It becomes much more interesting when a search term is used rather than a URL.

By typing simple “rupert” in the omnibox the result from the request is:

["rupert",["rupert murdoch","rupert grint","rupert everett"],["","",""],[],
{"google:suggesttype":["QUERY","QUERY","QUERY"]}]

Chrome then auto-suggests “rupert murdoch” as the primary completion with the drop down also suggesting “rupert grint” and “rupert everett”

Incidentally, you don’t get Instant Search or Instant Browsing while in Chrome’s Incognito Windows event if it is turned on.

Can anybody say DDoS?

While the expansion of Google’s Instant Search feature into Chrome is fantastic, my first thought when I saw Instant Browsing was that it could be used as a way to mount a DDoS (Distributed Denial of Service) attack on a website especially those that may be running on less robust hosting plans. It is okay for Google to inundate their own web properties from their browser but what about other site owners?

If a web site is not expecting the deluge of requests coming from Chrome browsers then it may be saturated dealing with requests that the user isn’t likely to be all that interested in anyway, especially if intermediate results are bringing back 404 responses (or worse 500 responses if the server breaks badly on bad URLs).

Google have thought of this and there is a way to tell Chrome to stop sending requests that you don’t want. If you read the Chrome FAQ for web developers, you’ll see there is a section opting out of Instant URL Loading. In short, you detect a request header that Chrome has inserted into the request and if you want to opt out return a HTTP 403 status code.  This will then have Chrome blacklist that website for the remainder of the user’s session. This means that if the user comes back another day there will still be that initial hit, giving the web site administrators a chance to opt back in.

An instant browsing request looks like this:

GET http://colinmackay.co.uk/blog/task HTTP/1.1
Host: colinmackay.co.uk
Connection: keep-alive
X-Purpose: : preview
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

The important part is the X-Purpose header. This is what tells the server that the browser is rendering the page as part of the Instant Browsing feature.

Note: The FAQ states that the header is “X-Purpose: preview” but fiddler shows an extra colon in there (see above). If you are attempting to detect the mode of the browser this may become important to the way you detect it.

SQL Server User Group: SQL Injection Attacks

Examples

The examples were run against a copy of the Adventure Works database.

Required Tables

For the Second Order Demo you need the following table added to the Adventure Works database:

CREATE TABLE [dbo].[FavouriteSearch](
	[id] [int] IDENTITY(1,1) NOT NULL,
	[name] [nvarchar](128) NOT NULL,
	[searchTerm] [nvarchar](1024) NOT NULL
) ON [PRIMARY]

GO

Stored Procedure with dynamic SQL

This is the stored procedure from the last demo which shows the Stored Procedure dynamically building a SQL statement that is susceptible to a SQL Injection Attack.

CREATE procedure [dbo].[SearchProducts]
(
  @searchId int
)
AS
BEGIN

  DECLARE @searchTerm NVARCHAR(1024)
  SELECT @searchTerm = searchTerm FROM FavouriteSearch WHERE id = @searchId

  DECLARE @sql NVARCHAR(2000) =
  'SELECT ProductID, Name, ProductNumber, ListPrice
  FROM Production.Product
  WHERE DiscontinuedDate IS NULL
  AND ListPrice > 0.0
  AND Name LIKE ''%'+@searchTerm+'%''';

  EXEC (@sql);

END

 

Slide Deck

The slide deck is available for download.

Further Reading

During the talk I mentioned this lesson from history (why firewalls are not enough), I also showed XKCD’s famous “Bobby Tables” cartoon, and also a link to further information on dynamic SQL in Stored Procedures. More information about the badly displayed error messages can be found amongst two blog posts: What not to develop, and a follow up some months later.

I wrote an article on SQL Injection Attacks that you can read here.

Tip of the day: IE Quirks Mode Vs. Standards Mode

If you are setting the DOCTYPE declaration in an HTML page to define the standard your page complies with ensure that you don’t put anything before that DOCTYPE declaration.

Some browsers will ignore comments and such like before the DOCTYPE declaration but IE doesn’t and if there is a comment it will then ignore the DOCTYPE which then puts your browser into Quirks Mode (and that can screw up the rendering of your page)

So, if I put this at the top of the document:

<!-- This will trigger IE into quirks mode -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

Then quirks mode will be rendered.

However, if I put the comment after the DOCTYPE everthing is fine, like this:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- This won't trigger IE into quirks mode as the DOCTYPE is first -->

DDD South West Parallelisation Talk Overview

Examples

Here are all the examples from Saturday’s introductory talk on Parallelisation at DDD South West 2011.

Slide Deck

The slide deck is also available as a PDF file (15.9 Mb)

Parallelisation Talk examples – Cancelling Tasks

This example showed what happens when tasks are cancelled. In this example, some tasks will be able to run to completion, others will be cancelled and other won’t even get a chance to start because the cancellation token was signalled before the task gets a chance to start.

Here is the code for the cancellation example shown in the talk

class Program
{
    static void Main(string[] args)
    {
        const int numTasks = 9;

        // Set up the cancellation source and get the token.
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        CancellationToken token = tokenSource.Token;

        // Set up the tasks
        Task[] tasks = new Task[numTasks];
        for (int i = 0; i < numTasks; i++)
            tasks[i] = Task.Factory.StartNew(() => PerformTask(token), token);

        // Now the tasks are all set up, show the state.
        // Most will be WaitingToRun, some will be Running
        foreach (Task t in tasks.OrderBy(t => t.Id))
            Console.WriteLine("Tasks {0} state: {1}", t.Id, t.Status);

        // Give some of the tasks a chance to do something.
        Thread.Sleep(1500);

        // Cancel the tasks
        Console.WriteLine("Cancelling tasks");
        tokenSource.Cancel();
        Console.WriteLine("Cancellation Signalled");

        try
        {
            // Wait for the tasks to cancel if they've not already completed
            Task.WaitAll(tasks);
        }
        catch (AggregateException aex)
        {
            aex.Handle(ex =>
            {
                // Handle the cancelled tasks
                TaskCanceledException tcex = ex as TaskCanceledException;
                if (tcex != null)
                {
                    Console.WriteLine("Handling cancellation of task {0}", tcex.Task.Id);
                    return true;
                }

                // Not handling any other types of exception.
                return false;
            });
        }

        // Show the state of each of the tasks.
        // Some will be RanToCompletion, others will be Cancelled.
        foreach(Task t in tasks.OrderBy(t => t.Id))
            Console.WriteLine("Tasks {0} state: {1}", t.Id, t.Status);


        Console.WriteLine("Program End");
        Console.ReadLine();
    }

    static void PerformTask(CancellationToken token)
    {
        try
        {
            // The loop simulates work that can be cooperatively cancelled.
            Console.WriteLine("Task {0}: Starting", Task.CurrentId);
            for (int i = 0; i < 4; i++)
            {
                // Check for the cancellation to be signalled
                token.ThrowIfCancellationRequested();

                // Write out a little bit showing the progress of the task
                Console.WriteLine("Task {0}: {1}/4 In progress", Task.CurrentId, i + 1);
                Thread.Sleep(500); // Simulate doing some work
            }
            // By getting here the task will RunToCompletion even if the
            // token has been signalled.
            Console.WriteLine("Task {0}: Finished", Task.CurrentId);
        }
        catch (OperationCanceledException)
        {
            // Any clean up code goes here.
            Console.WriteLine("Task {0}: Cancelling", Task.CurrentId);
            throw; // To ensure that the calling code knows the task was cancelled.
        }
        catch(Exception)
        {
            // Clean up other stuff
            throw; // If the calling code also needs to know.
        }
    }
}

 

Here is the output of the program (your results may vary):

Task 1: Starting
Task 1: 1/4 In progress
Task 2: Starting
Task 2: 1/4 In progress
Tasks 1 state: Running
Task 3: Starting
Task 3: 1/4 In progress
Tasks 2 state: Running
Task 4: Starting
Task 4: 1/4 In progress
Tasks 3 state: Running
Tasks 4 state: Running
Tasks 5 state: WaitingToRun
Tasks 6 state: WaitingToRun
Tasks 7 state: WaitingToRun
Tasks 8 state: WaitingToRun
Tasks 9 state: WaitingToRun
Task 1: 2/4 In progress
Task 2: 2/4 In progress
Task 3: 2/4 In progress
Task 4: 2/4 In progress
Task 1: 3/4 In progress
Task 2: 3/4 In progress
Task 4: 3/4 In progress
Task 3: 3/4 In progress
Task 1: 4/4 In progress
Task 2: 4/4 In progress
Task 4: 4/4 In progress
Task 3: 4/4 In progress
Task 5: Starting
Task 5: 1/4 In progress

To this point the tasks have been given a chance to operate normally. The tasks that have started are outputing to the console their progress. The main thread reports on the state of the tasks and shows tasks 1 to 4 are Running while the remainder are WaitingToRun. After a while the scheduler decides to start task 5.

Next the tasks are going to be cancelled.

Cancelling tasks
Cancellation Signalled
Task 1: Finished
Task 2: Finished
Task 4: Finished
Task 3: Finished
Task 5: Cancelling

When the cancellation token is signalled the tasks have to cooperate. Tasks 1 to 4 are too far gone and will run to completion. Task 5, which was only just started, cooperates with the cancellation request and writes that it is cancelling. No waiting tasks are started.

In the main thread, the control is blocked until all the tasks have either finished or cooperate with the cancellation request. Once the WaitAll unblocks the program handles any cancelled tasks in the catch block.

Handling cancellation of task 9
Handling cancellation of task 8
Handling cancellation of task 7
Handling cancellation of task 6
Handling cancellation of task 5

Tasks 6 to 9 never got a chance to start. Task 5 was started, but was cancelled. Therefore task 5’s cancellation can be handled inside the task and outside it. Different clean up may be required in each place.

Finally, the program lists the end state (See also: Task state transitions) of each of the tasks:

Tasks 1 state: RanToCompletion
Tasks 2 state: RanToCompletion
Tasks 3 state: RanToCompletion
Tasks 4 state: RanToCompletion
Tasks 5 state: Canceled
Tasks 6 state: Canceled
Tasks 7 state: Canceled
Tasks 8 state: Canceled
Tasks 9 state: Canceled
Program End

When writing code to handle cancelled tasks, watch out for this gotcha that can trip you up if you are not careful.

Tip of the day: Splitting a string when encountering whitespace

In .NET the string class has a Split method that splits the string at the separator character(s) that you specify. However, if you want to split the string at any instance of whitespace you don’t have to create a Split call that enumerates all those different types of whitespace… and there are actually quite a lot! Instead you can just call Split without any parameters and it will split at whitespace regardless of the type.

For example, the following program, in which I hope I’ve managed to use all the different types of whitespace in Unicode, will produce the output below:

static void Main(string[] args)
{
  string source = "Anu0020inspiredrcalligrapherncanu1680createu180epagesu2000ofu2001"+
    "beautytusingu2002sticku2003ink,u2004quill,u2005brush,u2006pick-axe,u2007buzzu2008"+
    "saw,u2009oru200aevenu202fstrawberryu205fjam."+
    Environment.NewLine+
    "Theu3000quicku2028brownu2029foxu0009jumpsu000aoveru000btheu000clazyu000ddog."+
    Environment.NewLine+
    "Whitespaceu0085Foru00a0the win!";

  string[] words = source.Split();

  foreach(string word in words)
  {
    Console.WriteLine(word);
  }
}

Produces this output:

An
inspired
calligrapher
can
create
pages
of
beauty
using
stick
ink,
quill,
brush,
pick-axe,
buzz
saw,
or
even
strawberry
jam.

The
quick
brown
fox
jumps
over
the
lazy
dog.

Whitespace
For
the
win!

Parallel Loop Anti-pattern

Here’s a quick parallel loop anti-pattern. In other words, don’t do this, it will only make you miserable.

If you want to start tasks in a loop watch out for including the loop variable as a closure to the task body. For example:

Task[] tasks = new Task[20];
for (int i = 0; i < 20; i++)
    tasks[i] = Task.Factory.StartNew(
        () => Console.WriteLine("The loop index is {0}", i));
Task.WaitAll(tasks);

What happens is that the variable i is updated in each loop iteration. So, the task uses the value of i as it is when the task runs. Since the task does not run in the loop (it may run at any time the task schedule sees fit to run the task) the value of i could have updated by the time the task actually runs.

In the example program above, my output showed that i was 20 for every single iteration. Incidentally, if you are using i for an indexer you’ll notice that 20 will be out of range for something with 20 elements (which uses indexes 0 to 19) which just adds to the misery.

If you have something like ReSharper installed the it will warn you that you “Access to modified closure” and underline the uses of i that are affected.

So, if you must use run the body of a loop in parallel you are much better off using Parallel.For than trying the above.

Task status state changes

Over the course of the last couple of months I’ve blogged a lot about the Task Parallel Library and mentioned a number of statuses that a task can have, but nowhere is a nice handy-dandy chart to show what those statuses are and how the transition from one to another. The green status show the normal line that most tasks will take. The purple and red show alternative paths that may be taken.

When a task is first created it status is Created – however you will almost never see this. This status only exists when you create a task using its constructor. Most of the time you would create tasks with Task.Factory.StartNew(…)

When you start a task the status will be WaitingToRun until such time as the task scheduler can actually run the task. For some tasks the transition will be almost instant, for others it will mean a wait while other tasks complete.

From WaitingToRun a task can transition to either Running or Cancelled. The latter transition happens if the cancellation token is signalled before the task gets a chance to start.

Once a task is running there are three possible exits for it. The normal exit is for the status to transition to RanToCompletion which means that the task completed without incident. The other two exits require exception handling in the calling code.

If an error happens that the task cannot (or does not) handle internally then it will transition to Faulted (see Tasks that throw exceptions). The AggregateException will contain details of all the exceptions in Faulted tasks as part of its InnerExceptions collection.

If the tasks are cancelled, any task that has not already completed will transition immediately to Cancelled. An AggregateException will contain details of all tasks that were cancelled (whether they had a chance to run or not) with TaskCanceledException inner exceptions. The exception to this is any task that was running already when the cancellation token was signalled and either ran to completion normally, or did not respond correctly to the cancellation request (see Cancelling parallel tasks).

Cancelling parallel tasks

UPDATE (7-June-2011): The post as it originally appeared had a bug in the code, the catch block in the task caught the wrong exception type. See the Gotcha section at the end for an explanation on why there are two types of exception for this.

I think, to date, I’ve mentioned most of the task lifecycle, but I’ve not talked about cancelling tasks yet. So here goes.

You can cancel tasks for what ever reason by passing in a cancellation token to the task. The task must be cooperative insomuch as it must watch the cancellation token to detect if a cancellation has been signalled then it can clean up and exit.

The basic program

So, the little example program to demonstrate this is this:

class Program
{
    static void Main(string[] args)
    {
        const int numTasks = 9;
        Task[] tasks = new Task[numTasks];
        for (int i = 0; i < 10; i++)
            tasks[i] = Task.Factory.StartNew(PerformTask);

        Task.WaitAll(tasks);

        foreach(Task t in tasks)
            Console.WriteLine("Tasks {0} state: {1}", t.Id, t.Status);

        Console.WriteLine("Program End");
        Console.ReadLine();
    }

    static void PerformTask()
    {
        Console.WriteLine("Task {0}: Starting", Task.CurrentId);
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine("Task {0}: {1}/3 In progress", Task.CurrentId, i+1);
            Thread.Sleep(500); // Simulate doing some work
        }
        Console.WriteLine("Task {0}: Finished", Task.CurrentId);
    }
}

So far this doesn’t do much. It starts 9 tasks and each runs to completion. Each task’s end state is RanToCompletion.

Setting up the CancellationToken

Now, if we introduce the cancellation token to the task we can cancel the task at some point during its execution. The Main method then gets changed to this:

static void Main(string[] args)
{
    const int numTasks = 9;

    CancellationTokenSource tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;

    Task[] tasks = new Task[numTasks];
    for (int i = 0; i < numTasks; i++)
        tasks[i] = Task.Factory.StartNew(() => PerformTask(token), token);

    Thread.Sleep(1500);
    Console.WriteLine("Cancelling tasks");
    tokenSource.Cancel();
    Console.WriteLine("Cancellation Signalled");

    Task.WaitAll(tasks);

    foreach(Task t in tasks)
        Console.WriteLine("Tasks {0} state: {1}", t.Id, t.Status);


    Console.WriteLine("Program End");
    Console.ReadLine();
}

The PerformTask method now takes a CancellationToken (but doesn’t yet do anything with it)

If this code is run, the Task.WaitAll method call will throw an AggregateException with a number of TaskCanceledException objects.

Handling cancelled tasks

You therefore have to surround your WaitAll method with a try/catch block and look out for TaskCanceledException objects and handle them as you need (see also: handling AggregateException exceptions). In my example I’m just going to output the fact to the console. The try/catch block looks like this:

try
{
    Task.WaitAll(tasks);
}
catch (AggregateException aex)
{
    aex.Handle(ex =>
    {
        TaskCanceledException tcex = ex as TaskCanceledException;
        if (tcex != null)
        {
            Console.WriteLine("Handling cancellation of task {0}", tcex.Task.Id);
            return true;
        }
        return false;
    });
}

The tasks that were in progress at the time the cancel was signalled complete as normal. Cancelling the tasks will not stop any currently running task.

Responding to a cancellation request: IsCancellationRequested

If the tasks are sufficiently short running allowing them to complete may be perfectly acceptable.

However, if a task is long running or it is safe to cancel them then you can allow your task to cooperate and respond to the token being signalled to cancel.

The CancellationToken object has a property you can check called IsCancellationRequested. For example:

static void PerformTask(CancellationToken token)
{
    Console.WriteLine("Task {0}: Starting", Task.CurrentId);
    for (int i = 0; i < 4; i++)
    {
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("Task {0}: Cancelling", Task.CurrentId);
            return;
        }
        Console.WriteLine("Task {0}: {1}/3 In progress", Task.CurrentId, i+1);
        Thread.Sleep(500); // Simulate doing some work
    }
    Console.WriteLine("Task {0}: Finished", Task.CurrentId);
}

If you simply exit from your task, like the above example, then the Status of the task will be RanToCompletion as if the task completed normally. If you do not need to know whether a task actually completed or was cancelled then this may be completely acceptable.

Responding to a cancellation request: ThrowIfCancellationRequested

If you need to perform clean up or the calling code needs to know that a task has been cancelled then using the CancellationToken’s ThrowIfCancellationRequested() method may be a better choice.

If you do need to perform clean up inside your task, ensure that the OperationCanceledExcption is thrown again so that the calling code knows that the task was cancelled.

static void PerformTask(CancellationToken token)
{
    try
    {
        Console.WriteLine("Task {0}: Starting", Task.CurrentId);
        for (int i = 0; i < 4; i++)
        {
            token.ThrowIfCancellationRequested();
            Console.WriteLine("Task {0}: {1}/3 In progress", Task.CurrentId, i + 1);
            Thread.Sleep(500); // Simulate doing some work
        }
        Console.WriteLine("Task {0}: Finished", Task.CurrentId);
    }
    catch (OperationCanceledException)
    {
        // Any clean up code goes here.
        Console.WriteLine("Task {0}: Cancelling", Task.CurrentId);
        throw; // To ensure that the calling code knows the task was cancelled.
    }
    catch(Exception)
    {
        // Clean up other stuff
        throw; // If the calling code also needs to know.
    }
}

Remember that if you allow other exceptions to escape your task then the task’s status will be Faulted.

Gotcha!

This section was added on 7-June-2011.

One thing to watch out for is that the exception you get inside the task is different from the exception you get inside the AggregateException outside the task. Normally, you’d expect that the exception is passed through and becomes one of the InnerExceptions in the aggregate exceptions.

It you want to keep the code consistent and only deal with one exception type for cancelled tasks you can simple deal with the OperationCanceledException throughout (both inside and outside the tasks) as that is the base class. Outside the task the exception object is actually a TaskCanceledException.

The advantage of referencing the more specific TaskCanceledException outside the task is that the exception object also contains a reference to the Task that was cancelled. Inside the task the exception that ThrowIfCancellationRequested throws is an OperationCanceledException (which doesn’t contain the Task object, however you are inside the task at this point)

The other point to note is that outside the task, the TaskCanceledException object in the AggregateException object doesn’t contain much of the information you’d expect to find in an Exception object (such as a Stack Trace).