What’s new in Beta 1 for the Task Parallel Library? (Part 1/3)

Danny Shih

Related Posts:

The Task Parallel Library (TPL) has come a long way since its inception.  Over the course of several CTPs, it has evolved to be an important and central new component in the .NET Framework.  Most recently, TPL was released as part of mscorlib.dll in the Visual Studio 2010 and .NET Framework 4.0 CTP around the October 2008 timeframe.   However, due to the nature of large software projects, that release was actually based on code from back in July!  Needless to say, since last summer, we’ve invested a lot of effort into making sure that the right functionality is exposed through the right set of APIs.  And as a result, TPL has changed considerably.  In this set of posts, we’ll walk through some of the changes so you’ll be ready for the next preview release of .NET 4.0 (no guarantees at this time regarding when that will be).  Of course, as with any software project, TPL may change even more between now and when it’s released, so we’re very interested in any feedback that you may have!

In this first post, we’ll talk about some changes under the covers, some redesigns in System.Threading.Parallel, and some new cancellation features (Tasks and Tokens).

Under the Covers

TPL now uses the .NET ThreadPool as its default scheduler.  As part of this effort, the ThreadPool has undergone a number of significant functional improvements:

·         Work-stealing queues were introduced internally to be used by TPL (see Daniel Moth’s post on the new CLR 4 ThreadPool engine)

·         Hill-climbing algorithms were introduced to quickly determine and adjust to the optimal number of threads for the current workload.

·         Coordination and synchronization types such as SpinWait and SpinLock are now used internally.

Also, the whole of Parallel Extensions just emerged from a performance push.  In TPL, this included work to decrease the overheads of loops and the launching of Tasks.  We’re by no means done where performance is concerned, but you should notice improved performance for a variety of scenarios.

System.Threading.Parallel

The TPL feature crew spent many hours in design meetings, and this has resulted in quite a few changes for our Parallel APIs.  Here are the most significant ones.

ParallelOptions

A common request/question we’ve gotten based on previous CTPs is the ability to limit the concurrency level of parallel loops.  Folks would create a new TaskManager (specifying the number of processors and/or the number of threads per processor) just to achieve this scenario, and many were still unsuccessful.  We now provide a better, more intuitive solution.

The new ParallelOptions class contains properties relevant to the APIs in System.Threading.Parallel.  One of these properties is MaxDegreeOfParallelism, which does what it sounds like it does.  The default value, -1, causes a Parallel API to attempt to use all available cores, but this can be overridden.  For example, the following loop will run on no more than two cores regardless of how many exist in the machine:

var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };

Parallel.For(0, 1000, options, i=>

{

   

});

 

By consolidating options into the ParallelOptions class, we were able to eliminate quite a few existing overloads and prevent exploding the number of overloads when adding new options.  Some new properties include a CancellationToken that will be monitored to determine whether a Parallel call should exit early, and a TaskScheduler that can be used to specify the scheduler on which to execute.  Both of these options are explored more in later sections.

Thread-local State

In previous releases, we supported thread-local state via a ParallelState<TLocal> class.  For example, to get the sum of 0-99:

int sum = 0;

Parallel.For(0, 100,

    // Initialize all thread-local states to 0.

    () => { return 0; },

 

    // Accumulate the iteration count.

    (int i, ParallelState<int> loopState) =>

    {

        loopState.ThreadLocalState += i;

    },

 

    // Accumulate all the final thread-local states.

    (int finalThreadLocalState) =>

    {

        Interlocked.Add(ref sum, finalThreadLocalState);

    });

In the above, loopState (a ParallelState<int> instance) stores each of the thread-local states in a property.  However, loopState would also be used to prematurely break out of the loop (using the Break or Stop methods).  For a cleaner design, we decided to separate these two functionalities by:

·         Renaming ParallelState to ParallelLoopState (used to break out of loops prematurely, check if a loop has been stopped by another iteration, etc.)

·         Removing ParallelState<TLocal> and baking thread-local state into the signatures of Parallel.For and ForEach overloads

To achieve the above scenario now, the body delegate would be:

    // Accumulate the iteration count.

    (int i, ParallelLoopState loopState, int threadLocalState) =>

    {

        return threadLocalState + i;

    },

Note that the body delegate is now a Func that returns a TLocal – in this case, an int.  Each iteration of the body is passed the current thread-local state and must return the possibly-updated state.

Tasks and Tokens

In the previous section, we saw that a CancellationToken may be used to cancel a Parallel call.  This token structure is actually part of a new unified cancellation model that is intended for eventual use throughout the .NET Framework.  As of Beta 1, it is supported by a few TPL APIs such as Wait:

Task t = …

CancellationTokenSource tokenSource = new CancellationTokenSource();

CancellationToken token = tokenSource.Token;

 

try { t.Wait(token); }

catch (OperationCanceledException oce) { }

// Elsewhere…

tokenSource.Cancel();

 

The new cancellation model centers around two types: CancellationTokenSource and CancellationToken.  A CancellationTokenSource is used to cancel its member CancellationToken (accessible via the Token property).  A CancellationToken can only be used to check whether cancellation has been requested; separating the ability to cancel and the ability to check for cancellation requests is a key point in the new model.  In the above example, the Wait operation is passed a CancellationToken, and the associated CancellationTokenSource is used to cancel the operation; note that it is the Wait operation that gets canceled, not the Task.

As mentioned in the “What’s new in CDS” post, cancellation merits a dedicated post, so look for that one soon.

Check back soon for “What’s new in Beta 1 for TPL (Part 2/3)”!

0 comments

Discussion is closed.

Feedback usabilla icon