Introduction

Task Parallel Library (TPL) is a set of new .NET Framework 4.0 Parallel Computing classes for easing the pain of building applications that leverage today's multi-core machines. Prior articles introduced the Task class, Understanding Tasks in .NET Framework 4.0 Task Parallel Library and Introducing the .NET Framework 4.0 Task Parallel Library BlockingCollection.
There is more, however, to TPL than Tasks and BlockingCollections. For example, there are patterns for composing Tasks implemented using other data structures and there data structures for synchronizing access to shared state. Using the Dining Philosophers example from the Framework samples, I'm going to explain how TPL data structures can be employed to organize Tasks and synchronize shared state.

Dining Philosophers?

Synchronizing access to shared state is one of the things that makes Parallel Computing so hard. Race conditions occur when separate threads don't synchronize access to a shared place in memory. The shared memory may be a single variable or a collection of data structures. For example, a variable containing the number of items in an array should be in sync with the physical number of array items.

To ensure proper synchronization parallel solutions "lock" a critical section of code. Locking, however introduces a new problem, the deadlock. Deadlocks occur when, for example, two threads have locked a piece of memory the other thread needs to continue execution. To avoid a deadlock, a program will often implement an algorithm that exercises special care to avoid or detect a deadlock.

The Dining Philosophers problem shows how a seemingly simple set of conditions can lead to deadlocks. Main elements of the problem are as follows:

A group of Philosophers are sitting at a table alternating between eating and thinking.

To the left and right of each Philosopher are eating utensils.

In order to eat a Philosopher must pick up a pair of utensils.

As you may have guessed a deadlock occurs if all the Philosophers grab the first available utensils without regard to what their neighbor is doing.

Sample Code Overview

Dining Philosophers TPL sample attempts to solve the problem using a variety of techniques and data structures built into TPL. The sample solves the problem using the following set of approaches.

SyncWithWaitAll utilizes Tasks alongside a WaitHandle. Each Philosopher runs on a separate Thread. Work is simulated using Thread.Sleep.

SyncWithOrderedForks solves the problem using the new in .NET Framework 4.0 SemphoreSlim data structure. Each Philosopher runs on a separate Thread. Work is simulated using Thread.Sleep.

Async composes a solution using Tasks, Iterators, and Continuations. This is a complicated example that showcases different ways to compose and link Tasks and leverage TaskCompletionSource classes. I'll focus most of the article on this example.

The TaskCreationOptions.LongRunning indicates that a Task will be running for a long time and "advises" TPL to create it as a separate thread. Utensils (forks) are implemented as Semaphores. SemaphoreSlim is a lighter weight version of the Semaphore class.

It's important to note that many of the monitors a developer may have used in other .NET version may have been reimplimented in another similarly named class with equivalent functionality, but more efficient underlying plumbing.

_ui.StartNew followed by "Wait" executes the color change on the user interface thread. WaitHandle.WaitAll blocks until both utensils are released. The other sample performs 2 successive waits on the SemaphoreSlim class.

That covers the more straightforward examples, the last example utilized some newer data structures and patterns. So I'm going to break the last example down into solution layers.

TaskFactory.Iterate is an extension function implemented in the TPL Samples. Main parts of the Iterate function appear below.

public static Task Iterate(
this TaskFactory factory,
IEnumerable<object> source, object state,
CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)
{
// Get an enumerator from the enumerable
var enumerator = source.GetEnumerator();
// Create the task to be returned to the caller. And ensure
// that when everything is done, the enumerator is cleaned up.
var trs = new TaskCompletionSource<object>(state, creationOptions);
trs.Task.ContinueWith(_ => enumerator.Dispose(), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
// This will be called every time more work can be done.
Action<Task> recursiveBody = null;
recursiveBody = antecedent =>
{
try
{
if (enumerator.MoveNext())
{
var nextItem = enumerator.Current;
// If we got a Task, continue from it to continue iterating
if (nextItem is Task)
{
var nextTask = (Task)nextItem;
nextTask.ContinueWith(recursiveBody).IgnoreExceptions();
}
}
// Otherwise, we're done!
else trs.TrySetResult(null);
}
….
};
// Get things started by launching the first task
factory.StartNew(() => recursiveBody(null), CancellationToken.None, TaskCreationOptions.None, scheduler).IgnoreExceptions();
// Return the representative task to the user
return trs.Task;

For now, I'll defer the discussion of the enumerator.MoveNext code and, instead, focus on what the other components are doing. TaskCompletionSource wraps a data structure around a Task allowing a developer to manipulate the return results of a Task. Creating a TaskCompletionSource also creates an internal Task class. In the example it was useful to wrap a Task that recursively consumes the iterator and spawning other Tasks; inside an outer Task being returned to code calling Iterate.

Trs.Task.ContinueWith attaches a Task that invokes the Dispose function to the Task being returned to the Iterate caller. Code passed to the ContinueWith method is called when the Task associated with ContinueWith function completes execution.

RecursiveBody is a block of code that is utilized in two places:

It's invoked when the nextTask completes execution. The ContinueWith on the nextTask sets this up.

The StartNew at the end of the function immediately invokes the code.

In the example above TrySetResult completes the Task from within RecursiveBody and unravels the recursion. Moving to the main portion of the Async sample I'll now explain the Enumerator.

The name of the function is a bit misleading. Wait does not block until a utensil is available. Instead it returns a Task that remains suspended until SetResult is called from the Release function in the AsnycSemaphone; setting the Task's result. The AsyncSemaphore Release method appears below:

About the Author

Jeffrey Juday

Jeff is a software developer specializing in enterprise application integration solutions utilizing BizTalk, SharePoint, WCF, WF, and SQL Server.
Jeff has been developing software with Microsoft tools for more than 15 years in a variety of industries including: military, manufacturing, financial services, management consulting, and computer security.
Jeff is a Microsoft BizTalk MVP.
Jeff spends his spare time with his wife Sherrill and daughter Alexandra.

Comments

There are no comments yet. Be the first to comment!

You must have javascript enabled in order to post comments.

Leave a Comment

Your email address will not be published. All fields are required.

Name

Email

Title

Comment

Top White Papers and Webcasts

The 2014 State of DevOps Report — based on a survey of 9,200+ people in IT operations, software development and technology management roles in 110 countries — reveals:
Companies with high-performing IT organizations are twice as likely to exceed their profitability, market share and productivity goals.
IT performance improves with DevOps maturity, and strongly correlates with well-known DevOps practices.
Job satisfaction is the No. 1 predictor of performance against organizational …