DDD Scotland Parallelisation Talk Overview

DDD Scotland 2011 Talk Opening Slide

Follow Up

I was asked a question during the talk I wasn’t immediately able to answer which I’ve now found the definite answer. What happens to parallel tasks running in the background when the application exits?

Examples

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

Slide Deck

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

Background tasks and application exit

During my talk on Parallelisation at DDD Scotland 2011 I was asked what happens if the application finishes while there were still tasks running.

At the time, I was showing the Tasks Within Tasks demo and I showed what happened when the Wait call was removed in the Main method. Since the Wait call was removed the lines following it were immediately executed. Those were: to output that the program was ending; and to wait for a key press (to give the audience time to read the console output).

So, what happens when the application naturally concludes like when the Main method completes? When preparing the talk that question had simply not occurred to me. Do the background tasks continue until completion or do they all stop? I suspected they would just stop.

During the talk I simply removed the Console.ReadLine call, but then the console window appeared and then disappeared in a tiny fraction of a second. Did the tasks stop? Or, did the tasks simply continue without a console window available?

Since a further investigation at the time would have disrupted the talk I said I’d investigate further and follow up on my blog. So, this is that follow up.

I’ve now created a new example where the tasks output to files rather than the console. If the tasks stop, the files either won’t be created or contain incomplete output. If they are allowed to continue then the files will be complete.

Here is the code:

class Program
{
    private static readonly string folderName = DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss");

    static void Main(string[] args)
    {
        // Create a directory for the output to go.
        Directory.CreateDirectory(folderName);

        // Start the tasks
        for (int i = 0; i < 20; i++)
            Task.Factory.StartNew(PerformTask, TaskCreationOptions.AttachedToParent);

        // Give the tasks a chance to start something.
        Thread.Sleep(750);
    }

    public static void PerformTask()
    {
        // Create a new file in the output directory
        string path = folderName + "\" + Task.CurrentId + ".txt";
        using (StreamWriter writer = File.CreateText(path))
        {
            // Ensures that all write operations are immediately flushed to disk
            writer.AutoFlush = true;

            // Write stuff to the file over a period of time
            writer.WriteLine("Starting Task {0}", Task.CurrentId);
            Thread.Sleep(500);
            writer.WriteLine("Ending Task {0}", Task.CurrentId);
        }
    }
}

In the Main method, the Sleep waits long enough that the first few tasks will run to completion and further queued tasks are started.

The result on my machine is that the first 4 tasks run to completion, the next 4 tasks are in still being processed when the Main method naturally concludes. At this point the application exits. All the running tasks are forced to exit leaving files with only the first part of their content in them.

So, in answer to the question I was asked: If the application finishes while there are tasks still running then all the running tasks are all stopped without being allowed to complete. No more queued tasks are started.

Parallelisation Talk Example – Aggregate Exceptions

The two code examples here show what happens when exceptions are thrown within tasks that are not handled within the task. In each case the task that has the error throws an exception.

In the first example, only one task throws an exception. Although from the output you can see that more tasks were expected to be launched the framework no longer schedules tasks to be started once an exception is thrown. Any existing tasks are continued to completion.

In the second example, all the tasks will throw exceptions. This is just to show that the aggregate exception is bringing back all the exceptions from the various tasks that were running.

Code Example 1 : Some bad

class Program
{
    static void Main(string[] args)
    {
        List<HotelRoomAvailability> hotelList = GetHotels();

        Console.WriteLine("These are the hotels to process");
        foreach(var hotel in hotelList)
            Console.WriteLine(hotel.HotelCode);

        Console.WriteLine(new string('=',79));


        try
        {
            Parallel.ForEach(hotelList, item => PopulateDetails(item));
        }
        catch (AggregateException aggex)
        {
            Console.WriteLine(aggex.Message);
            foreach(Exception ex in aggex.InnerExceptions)
                Console.WriteLine(ex.Message);
        }


        Console.WriteLine("Program finished");
        Console.ReadLine();

    }

    private static void PopulateDetails(HotelRoomAvailability hotel)
    {
        Console.WriteLine("Populating details of {0}", hotel.HotelCode);
        hotel.Name = HotelRespository.GetHotelName(hotel.HotelCode);
        hotel.Rates = AvailabilityRespository.GetRateInformation(
            hotel.HotelCode, hotel.StayDate, hotel.NumberOfNights);
    }

    private static List<HotelRoomAvailability> GetHotels()
    {
        List<HotelRoomAvailability> result = new List<HotelRoomAvailability>
            {
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONSOHO"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONLHRT4"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONLHRT5" // Not valid
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONWATERL" // Not valid
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONLHR123"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONCOVGDN"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONCTYAIR"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONLEISQR"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONPADDIN" // Not Valid
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONHIGHOL"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONKINGSX"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONEUSTON"
                    }
            };

        return result;
    }
}

Output

The following is output first

These are the hotels to process
LONSOHO
LONLHRT4
LONLHRT5
LONWATERL
LONLHR123
LONCOVGDN
LONCTYAIR
LONLEISQR
LONPADDIN
LONHIGHOL
LONKINGSX
LONEUSTON
========================================================
Populating details of LONSOHO
Populating details of LONWATERL
Populating details of LONCTYAIR
Populating details of LONHIGHOL
Populating details of LONLHRT4

 

Aggregate Exception Example - Before

Then an exception is thrown

Aggregate Exception Example - Exception Assistant

And the final output looks like this:

These are the hotels to process
LONSOHO
LONLHRT4
LONLHRT5
LONWATERL
LONLHR123
LONCOVGDN
LONCTYAIR
LONLEISQR
LONPADDIN
LONHIGHOL
LONKINGSX
LONEUSTON
========================================================
Populating details of LONSOHO
Populating details of LONWATERL
Populating details of LONCTYAIR
Populating details of LONHIGHOL
Populating details of LONLHRT4
One or more errors occurred.
The hotel code 'LONWATERL' does not match a known hotel
Program finished

 

Aggregate Exception Example - After

Code Example 2 : All bad

This example replaces the GetHotels method, above, with a method that creates a list of entirely non-existant hotels:

class Program
{
    static void Main(string[] args)
    {
        List<HotelRoomAvailability> hotelList = GetHotels();

        Console.WriteLine("These are the hotels to process");
        foreach(var hotel in hotelList)
            Console.WriteLine(hotel.HotelCode);

        Console.WriteLine(new string('=',79));


        try
        {
            Parallel.ForEach(hotelList, item => PopulateDetails(item));
        }
        catch (AggregateException aggex)
        {
            Console.WriteLine(aggex.Message);
            foreach(Exception ex in aggex.InnerExceptions)
                Console.WriteLine(ex.Message);
        }


        Console.WriteLine("Program finished");
        Console.ReadLine();

    }

    private static void PopulateDetails(HotelRoomAvailability hotel)
    {
        Console.WriteLine("Populating details of {0}", hotel.HotelCode);
        hotel.Name = HotelRespository.GetHotelName(hotel.HotelCode);
        hotel.Rates = AvailabilityRespository.GetRateInformation(
            hotel.HotelCode, hotel.StayDate, hotel.NumberOfNights);
    }

    private static List<HotelRoomAvailability> GetHotels()
    {
        List<HotelRoomAvailability> result = new List<HotelRoomAvailability>
            {
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "BRISTOL"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "BIRMINGHAM"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "MANCHESTER" // Not valid
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LIVERPOOL" // Not valid
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "CARLISLE"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "CAMBRIDGE"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "OXFORD"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "READING"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LEEDS" // Not Valid
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "NEWCASTLE"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "EDINBURGH"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "GLASGOW"
                    }
            };

        return result;
    }
}

Output

These are the hotels to process
BRISTOL
BIRMINGHAM
MANCHESTER
LIVERPOOL
CARLISLE
CAMBRIDGE
OXFORD
READING
LEEDS
NEWCASTLE
EDINBURGH
GLASGOW
============================================================
Populating details of BRISTOL
Populating details of LIVERPOOL
Populating details of OXFORD
Populating details of NEWCASTLE
Populating details of BIRMINGHAM
One or more errors occurred.
The hotel code 'LIVERPOOL' does not match a known hotel
The hotel code 'OXFORD' does not match a known hotel
The hotel code 'NEWCASTLE' does not match a known hotel
The hotel code 'BIRMINGHAM' does not match a known hotel
The hotel code 'BRISTOL' does not match a known hotel
Program finished

More Information

Parallelisation talk example – Independent Object Graphs

Parallelised code works best when data is not shared. This example shows a simple piece of parallel code where each task operates independently on its own object graph without dependencies on other objects outside its own graph.

Each iteration of the Parallel.ForEach statement operates on only one item in the List named initialObjectGraph. The operations do not make reference to any other objects in the list. The PopulateDetails method (which effectively operates on each item in the list) only uses and updates the information in just that one item.

The program outputs the initial object graph with just the very basic information that is input into it in the GetObjectGraph method. After the Parallel.ForEach has called PopulateDetails on each element in the list, the object graph is output once again

Example Code

class Program
{
    static void Main(string[] args)
    {
        List<HotelRoomAvailability> initialObjectGraph = GetObjectGraph();

        DisplayObjectGraph(initialObjectGraph);

        Parallel.ForEach(initialObjectGraph, item => PopulateDetails(item));

        DisplayObjectGraph(initialObjectGraph);

        Console.WriteLine("Program finished");
        Console.ReadLine();

    }

    private static void PopulateDetails(HotelRoomAvailability hotel)
    {
        Console.WriteLine("Populating details of {0}", hotel.HotelCode);
        hotel.Name = HotelRespository.GetHotelName(hotel.HotelCode);
        hotel.Rates = AvailabilityRespository.GetRateInformation(
            hotel.HotelCode, hotel.StayDate, hotel.NumberOfNights);
    }

    private static List<HotelRoomAvailability> GetObjectGraph()
    {
        List<HotelRoomAvailability> result = new List
            {
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONSOHO"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONCOVGDN"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONLEISQR"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONHIGHOL"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONKINGSX"
                    },
                new HotelRoomAvailability
                    {
                        StayDate = new DateTime(2011, 7, 1),
                        NumberOfNights = 3,
                        HotelCode = "LONEUSTON"
                    }
            };

        return result;
    }

    private static void DisplayObjectGraph(
        IEnumerable initialObjectGraph)
    {
        foreach (HotelRoomAvailability availability in initialObjectGraph)
        {
            Console.WriteLine(availability);
            Console.WriteLine(new string('=', 80));
        }
    }
}

Note that not all code is being shown here. There are a number of classes outside the Program class that operate on the data simulated calls to services or other calculations on the data. If you want to see the full example please download the full code sample using the link at the bottom of this post.

Example output

LONSOHO :
Staying on 01/Jul/2011 for 3 nights

================================================================================

LONCOVGDN :
Staying on 01/Jul/2011 for 3 nights

================================================================================

LONLEISQR :
Staying on 01/Jul/2011 for 3 nights

================================================================================

LONHIGHOL :
Staying on 01/Jul/2011 for 3 nights

================================================================================

LONKINGSX :
Staying on 01/Jul/2011 for 3 nights

================================================================================

LONEUSTON :
Staying on 01/Jul/2011 for 3 nights

================================================================================

Populating details of LONSOHO
Populating details of LONCOVGDN
Populating details of LONLEISQR
Populating details of LONHIGHOL
Populating details of LONKINGSX
Populating details of LONEUSTON
LONSOHO : London Soho
Staying on 01/Jul/2011 for 3 nights
Available Rates:
Rate: One Month Advanced Rate Room Only
01/Jul/2011: £48.40
02/Jul/2011: £52.80
03/Jul/2011: £44.00
Total Price: £145.20
----------------------------------------
Rate: One Month Advanced Rate Bed and Breakfast
01/Jul/2011: £58.40
02/Jul/2011: £62.80
03/Jul/2011: £54.00
Total Price: £175.20
----------------------------------------
Rate: One Month Advanced Rate Dinner, Bed and Breakfast
01/Jul/2011: £83.40
02/Jul/2011: £87.80
03/Jul/2011: £79.00
Total Price: £250.20
----------------------------------------
Rate: Two Week Advances Rate Room Only
01/Jul/2011: £54.45
02/Jul/2011: £59.40
03/Jul/2011: £49.50
Total Price: £163.35
----------------------------------------
Rate: Two Week Advances Rate Bed and Breakfast
01/Jul/2011: £64.45
02/Jul/2011: £69.40
03/Jul/2011: £59.50
Total Price: £193.35
----------------------------------------
Rate: Two Week Advances Rate Dinner, Bed and Breakfast
01/Jul/2011: £89.45
02/Jul/2011: £94.40
03/Jul/2011: £84.50
Total Price: £268.35
----------------------------------------
Rate: Fully Flexible Rate Room Only
01/Jul/2011: £60.50
02/Jul/2011: £66.00
03/Jul/2011: £55.00
Total Price: £181.50
----------------------------------------
Rate: Fully Flexible Rate Bed and Breakfast
01/Jul/2011: £70.50
02/Jul/2011: £76.00
03/Jul/2011: £65.00
Total Price: £211.50
----------------------------------------
Rate: Fully Flexible Rate Dinner, Bed and Breakfast
01/Jul/2011: £95.50
02/Jul/2011: £101.00
03/Jul/2011: £90.00
Total Price: £286.50
----------------------------------------

================================================================================

LONCOVGDN : London Covent Garden
Staying on 01/Jul/2011 for 3 nights
Available Rates:
Rate: One Month Advanced Rate Room Only
01/Jul/2011: £59.84
02/Jul/2011: £65.28
03/Jul/2011: £54.40
Total Price: £179.52
----------------------------------------
Rate: One Month Advanced Rate Bed and Breakfast
01/Jul/2011: £69.84
02/Jul/2011: £75.28
03/Jul/2011: £64.40
Total Price: £209.52
----------------------------------------
Rate: One Month Advanced Rate Dinner, Bed and Breakfast
01/Jul/2011: £94.84
02/Jul/2011: £100.28
03/Jul/2011: £89.40
Total Price: £284.52
----------------------------------------
Rate: Two Week Advances Rate Room Only
01/Jul/2011: £67.32
02/Jul/2011: £73.44
03/Jul/2011: £61.20
Total Price: £201.96
----------------------------------------
Rate: Two Week Advances Rate Bed and Breakfast
01/Jul/2011: £77.32
02/Jul/2011: £83.44
03/Jul/2011: £71.20
Total Price: £231.96
----------------------------------------
Rate: Two Week Advances Rate Dinner, Bed and Breakfast
01/Jul/2011: £102.32
02/Jul/2011: £108.44
03/Jul/2011: £96.20
Total Price: £306.96
----------------------------------------
Rate: Fully Flexible Rate Room Only
01/Jul/2011: £74.80
02/Jul/2011: £81.60
03/Jul/2011: £68.00
Total Price: £224.40
----------------------------------------
Rate: Fully Flexible Rate Bed and Breakfast
01/Jul/2011: £84.80
02/Jul/2011: £91.60
03/Jul/2011: £78.00
Total Price: £254.40
----------------------------------------
Rate: Fully Flexible Rate Dinner, Bed and Breakfast
01/Jul/2011: £109.80
02/Jul/2011: £116.60
03/Jul/2011: £103.00
Total Price: £329.40
----------------------------------------

================================================================================

LONLEISQR : London Leicester Square
Staying on 01/Jul/2011 for 3 nights
Available Rates:
Rate: One Month Advanced Rate Room Only
01/Jul/2011: £61.60
02/Jul/2011: £67.20
03/Jul/2011: £56.00
Total Price: £184.80
----------------------------------------
Rate: One Month Advanced Rate Bed and Breakfast
01/Jul/2011: £71.60
02/Jul/2011: £77.20
03/Jul/2011: £66.00
Total Price: £214.80
----------------------------------------
Rate: One Month Advanced Rate Dinner, Bed and Breakfast
01/Jul/2011: £96.60
02/Jul/2011: £102.20
03/Jul/2011: £91.00
Total Price: £289.80
----------------------------------------
Rate: Two Week Advances Rate Room Only
01/Jul/2011: £69.30
02/Jul/2011: £75.60
03/Jul/2011: £63.00
Total Price: £207.90
----------------------------------------
Rate: Two Week Advances Rate Bed and Breakfast
01/Jul/2011: £79.30
02/Jul/2011: £85.60
03/Jul/2011: £73.00
Total Price: £237.90
----------------------------------------
Rate: Two Week Advances Rate Dinner, Bed and Breakfast
01/Jul/2011: £104.30
02/Jul/2011: £110.60
03/Jul/2011: £98.00
Total Price: £312.90
----------------------------------------
Rate: Fully Flexible Rate Room Only
01/Jul/2011: £77.00
02/Jul/2011: £84.00
03/Jul/2011: £70.00
Total Price: £231.00
----------------------------------------
Rate: Fully Flexible Rate Bed and Breakfast
01/Jul/2011: £87.00
02/Jul/2011: £94.00
03/Jul/2011: £80.00
Total Price: £261.00
----------------------------------------
Rate: Fully Flexible Rate Dinner, Bed and Breakfast
01/Jul/2011: £112.00
02/Jul/2011: £119.00
03/Jul/2011: £105.00
Total Price: £336.00
----------------------------------------

================================================================================

LONHIGHOL : London High Holborn
Staying on 01/Jul/2011 for 3 nights
Available Rates:
Rate: One Month Advanced Rate Room Only
01/Jul/2011: £59.84
02/Jul/2011: £65.28
03/Jul/2011: £54.40
Total Price: £179.52
----------------------------------------
Rate: One Month Advanced Rate Bed and Breakfast
01/Jul/2011: £69.84
02/Jul/2011: £75.28
03/Jul/2011: £64.40
Total Price: £209.52
----------------------------------------
Rate: One Month Advanced Rate Dinner, Bed and Breakfast
01/Jul/2011: £94.84
02/Jul/2011: £100.28
03/Jul/2011: £89.40
Total Price: £284.52
----------------------------------------
Rate: Two Week Advances Rate Room Only
01/Jul/2011: £67.32
02/Jul/2011: £73.44
03/Jul/2011: £61.20
Total Price: £201.96
----------------------------------------
Rate: Two Week Advances Rate Bed and Breakfast
01/Jul/2011: £77.32
02/Jul/2011: £83.44
03/Jul/2011: £71.20
Total Price: £231.96
----------------------------------------
Rate: Two Week Advances Rate Dinner, Bed and Breakfast
01/Jul/2011: £102.32
02/Jul/2011: £108.44
03/Jul/2011: £96.20
Total Price: £306.96
----------------------------------------
Rate: Fully Flexible Rate Room Only
01/Jul/2011: £74.80
02/Jul/2011: £81.60
03/Jul/2011: £68.00
Total Price: £224.40
----------------------------------------
Rate: Fully Flexible Rate Bed and Breakfast
01/Jul/2011: £84.80
02/Jul/2011: £91.60
03/Jul/2011: £78.00
Total Price: £254.40
----------------------------------------
Rate: Fully Flexible Rate Dinner, Bed and Breakfast
01/Jul/2011: £109.80
02/Jul/2011: £116.60
03/Jul/2011: £103.00
Total Price: £329.40
----------------------------------------

================================================================================

LONKINGSX : London Kings Cross
Staying on 01/Jul/2011 for 3 nights
Available Rates:
Rate: One Month Advanced Rate Room Only
01/Jul/2011: £61.60
02/Jul/2011: £67.20
03/Jul/2011: £56.00
Total Price: £184.80
----------------------------------------
Rate: One Month Advanced Rate Bed and Breakfast
01/Jul/2011: £71.60
02/Jul/2011: £77.20
03/Jul/2011: £66.00
Total Price: £214.80
----------------------------------------
Rate: One Month Advanced Rate Dinner, Bed and Breakfast
01/Jul/2011: £96.60
02/Jul/2011: £102.20
03/Jul/2011: £91.00
Total Price: £289.80
----------------------------------------
Rate: Two Week Advances Rate Room Only
01/Jul/2011: £69.30
02/Jul/2011: £75.60
03/Jul/2011: £63.00
Total Price: £207.90
----------------------------------------
Rate: Two Week Advances Rate Bed and Breakfast
01/Jul/2011: £79.30
02/Jul/2011: £85.60
03/Jul/2011: £73.00
Total Price: £237.90
----------------------------------------
Rate: Two Week Advances Rate Dinner, Bed and Breakfast
01/Jul/2011: £104.30
02/Jul/2011: £110.60
03/Jul/2011: £98.00
Total Price: £312.90
----------------------------------------
Rate: Fully Flexible Rate Room Only
01/Jul/2011: £77.00
02/Jul/2011: £84.00
03/Jul/2011: £70.00
Total Price: £231.00
----------------------------------------
Rate: Fully Flexible Rate Bed and Breakfast
01/Jul/2011: £87.00
02/Jul/2011: £94.00
03/Jul/2011: £80.00
Total Price: £261.00
----------------------------------------
Rate: Fully Flexible Rate Dinner, Bed and Breakfast
01/Jul/2011: £112.00
02/Jul/2011: £119.00
03/Jul/2011: £105.00
Total Price: £336.00
----------------------------------------

================================================================================

LONEUSTON : London Euston
Staying on 01/Jul/2011 for 3 nights
Available Rates:
Rate: One Month Advanced Rate Room Only
01/Jul/2011: £62.48
02/Jul/2011: £68.16
03/Jul/2011: £56.80
Total Price: £187.44
----------------------------------------
Rate: One Month Advanced Rate Bed and Breakfast
01/Jul/2011: £72.48
02/Jul/2011: £78.16
03/Jul/2011: £66.80
Total Price: £217.44
----------------------------------------
Rate: One Month Advanced Rate Dinner, Bed and Breakfast
01/Jul/2011: £97.48
02/Jul/2011: £103.16
03/Jul/2011: £91.80
Total Price: £292.44
----------------------------------------
Rate: Two Week Advances Rate Room Only
01/Jul/2011: £70.29
02/Jul/2011: £76.68
03/Jul/2011: £63.90
Total Price: £210.87
----------------------------------------
Rate: Two Week Advances Rate Bed and Breakfast
01/Jul/2011: £80.29
02/Jul/2011: £86.68
03/Jul/2011: £73.90
Total Price: £240.87
----------------------------------------
Rate: Two Week Advances Rate Dinner, Bed and Breakfast
01/Jul/2011: £105.29
02/Jul/2011: £111.68
03/Jul/2011: £98.90
Total Price: £315.87
----------------------------------------
Rate: Fully Flexible Rate Room Only
01/Jul/2011: £78.10
02/Jul/2011: £85.20
03/Jul/2011: £71.00
Total Price: £234.30
----------------------------------------
Rate: Fully Flexible Rate Bed and Breakfast
01/Jul/2011: £88.10
02/Jul/2011: £95.20
03/Jul/2011: £81.00
Total Price: £264.30
----------------------------------------
Rate: Fully Flexible Rate Dinner, Bed and Breakfast
01/Jul/2011: £113.10
02/Jul/2011: £120.20
03/Jul/2011: £106.00
Total Price: £339.30
----------------------------------------

================================================================================

Program finished

More information

Prallelisation Talk Example – Tasks within Tasks

In this example I’m showing the launching of further tasks within an existing task.

The Main method launches a single task (of course, it would likely be many tasks in a real system) which is implemented by MainTask and then waits for that task to complete. The MainTask then launches many independent tasks (impelemnted as SubTask and attaches each of them to the parent task (in this case MainTask). This has the effect that when MainTask ends the code in Main is still blocked at the Wait call until all the child tasks have also completed.

Code Example

class Program
{
    static void Main(string[] args)
    {
        Task t = Task.Factory.StartNew(MainTask);
        t.Wait();

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

    public static void MainTask()
    {
        Console.WriteLine("Starting the Main Task");

        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);
        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);
        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);
        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);
        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);
        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);
        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);
        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);
        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);
        Task.Factory.StartNew(SubTask, TaskCreationOptions.AttachedToParent);

        Console.WriteLine("Ending the Main Task");
    }

    public static void SubTask()
    {
        Console.WriteLine("Starting SubTask {0}", Task.CurrentId);
        Thread.Sleep(2500);
        Console.WriteLine("Ending SubTask {0}", Task.CurrentId);
    }
}

 

Output

Tasks within Tasks Example

Starting the Main Task
Ending the Main Task
Starting SubTask 1
Starting SubTask 2
Starting SubTask 3
Starting SubTask 4
Starting SubTask 5
Starting SubTask 6
Ending SubTask 2
Starting SubTask 7
Ending SubTask 1
Starting SubTask 8
Ending SubTask 3
Starting SubTask 9
Ending SubTask 4
Starting SubTask 10
Ending SubTask 5
Ending SubTask 6
Ending SubTask 7
Ending SubTask 8
Ending SubTask 9
Ending SubTask 10
Program finished

See also

I have also writtent a blog post on Tasks that create more work which may give further insight into this area.

Parallelisation Talk Example – Parallel.Invoke

Parallel.Invoke is the most basic way to start many tasks as the same time. The method takes as many Action<…> based delegates as needed. The Task Parallel Library takes care of the actual scheduling, degree of parallelism etc. Parallel.Invoke itself blocks until all the tasks have completed.

In this example there are two tasks that simply output numbers and letters to the console. One task takes slightly longer than the other. The output shows each task as it runs along with an indication of when it finishes, as well as the overall program finishes.

Code Example One

class Program
{
    static void Main(string[] args)
    {
        // Start two tasks in parallel
        Parallel.Invoke(TaskOne, TaskTwo);

        Console.WriteLine("Finished");
        Console.ReadLine();
    }

    // This task simple outputs the numbers 0 to 9
    private static void TaskOne()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("TaskOne: {0}", i);
            Thread.Sleep(10);
        }
        Console.WriteLine("TaskOne Finished");
    }

    // This task simply outputs the letters A to K
    private static void TaskTwo()
    {
        for(char c = 'A'; c < 'K'; c++)
        {
            Console.WriteLine("TaskTwo: {0}", c);
            Thread.Sleep(5);
        }
        Console.WriteLine("TaskTwo Finished");
    }
}

Example output

Parallel.Invoke Example

TaskOne: 0
TaskTwo: A
TaskOne: 1
TaskTwo: B
TaskTwo: C
TaskTwo: D
TaskOne: 2
TaskTwo: E
TaskOne: 3
TaskTwo: F
TaskTwo: G
TaskTwo: H
TaskOne: 4
TaskTwo: I
TaskTwo: J
TaskOne: 5
TaskTwo Finished
TaskOne: 6
TaskOne: 7
TaskOne: 8
TaskOne: 9
TaskOne Finished
Finished

Code Example: Variation using Task.Factory.StartNew

The Parallel.Invoke method is equivalent setting up a number of tasks using Task.Factory.StartNew(…) then Task.WaitAll(…).

The following code example shows the same code (Main method only) but using Task.Factory.StartNew:

static void Main(string[] args)
{
    // Start two tasks in parallel
    Task t1 = Task.Factory.StartNew(TaskOne);
    Task t2 = Task.Factory.StartNew(TaskTwo);
    Task.WaitAll(t1, t2);

    Console.WriteLine("Finished");
    Console.ReadLine();
}

Parallelisation Talk Example – ConcurrentBag

This example shows a ConcurrentBag being populated and it being accessed while another task is still populating the bag.

The ConcurrentBag class can be found in the System.Collections.Concurrent namespace

In this example, the ConcurrentBag is populated in task that is running in the background. After a brief pause in order to allow the background task time to put some items in the bag, main thread starts outputting the contents of the bag.

When the code starts to iterate over the bag, a snapshot is taken so that the enumeration is not tripped up by additional items being added or removed from the bag elsewhere. You can see this effect as only 13 items are output, yet immediately afterwards the bag has 20 items (in this example, if you run the code yourself you may get different results)

Code Example

class Program
{
    private static ConcurrentBag<string> bag = new ConcurrentBag<string>();
    static void Main(string[] args)
    {
        // Start a task to run in the background.
        Task.Factory.StartNew(PopulateBag);

        // Wait a wee bit so that the bag can get populated
        // with some items before we attempt to output them.
        Thread.Sleep(25);

        // Display the contents of the bag
        int count = 0;
        foreach (string item in bag)
        {
            count++;
            Console.WriteLine(item);
        }

        // Show the difference between the count of items
        // displayed and the current state of the bag
        Console.WriteLine("{0} items were output", count);
        Console.WriteLine("The bag contains {0} items", bag.Count);

        Console.ReadLine();
    }

    public static void PopulateBag()
    {
        for (int i = 0; i < 200; i++ )
        {
            bag.Add(string.Format("This is item {0}", i));

            // Wait a bit to simulate other processing.
            Thread.Sleep(1);
        }

        // Show the final size of the bag.
        Console.WriteLine("Finished populating the bag with {0} items.", bag.Count);
    }
}

Typical output

ConcurrentBag Example

This is item 12
This is item 11
This is item 10
This is item 9
This is item 8
This is item 7
This is item 6
This is item 5
This is item 4
This is item 3
This is item 2
This is item 1
This is item 0
13 items were output
The bag contains 20 items
Finished populating the bag with 200 items.

Parallelisation Talk Examples – ConcurrentDictionary

The example used in the talk was one I had already blogged about. The original blog entry the example was based upon is here: Parallelisation in .NET 4.0 – The ConcurrentDictionary.

Code Example

class Program
{
    private static ConcurrentDictionary<string, int> wordCounts =
        new ConcurrentDictionary<string, int>();

    static void Main(string[] args)
    {
        string[] lines = File.ReadAllLines("grimms-fairy-tales.txt");
        Parallel.ForEach(lines, ProcessLine);

        Console.WriteLine("There are {0} distinct words", wordCounts.Count);
        var topForty = wordCounts.OrderByDescending(kvp => kvp.Value).Take(40);
        foreach (KeyValuePair word in topForty)
        {
            Console.WriteLine("{0}: {1}", word.Key, word.Value);
        }
        Console.ReadLine();
    }

    private static void ProcessLine(string line)
    {
        var words = line.Split(' ')
            .Select(w => w.Trim().ToLowerInvariant())
            .Where(w => !string.IsNullOrEmpty(w));
        foreach (string word in words)
            CountWord(word);
    }

    private static void CountWord(string word)
    {
        if (!wordCounts.TryAdd(word, 1))
            UpdateCount(word);
    }

    private static void UpdateCount(string word)
    {
        int value = wordCounts[word];
        if (!wordCounts.TryUpdate(word, value + 1, value))
        {
            Console.WriteLine("Failed to count '{0}' (was {1}), trying again...",
                word, value);

            UpdateCount(word);
        }
    }
}

Downloads

Parallelisation Talk Examples – Basic PLINQ

These are some code examples from my introductory talk on Parallelisation showing the difference between a standard sequential LINQ query and its parallel equivalent.

The main differences between this and the previous two examples (Parallel.For and Parallel.ForEach) is that LINQ (and PLINQ) is designed to return data back, so the LINQ expression uses a Func<TResult, T1, T2, T3…> instead of an Action<T1, T2, T3…>. Since the examples were simply outputting a string to the Console to indicate which item or index was being processed I’ve changed the code to return a string back to the LINQ expression. The results are then looped over and output to the console.

It is also important to remember that LINQ expressions are not evaluated until the data is called for. In the example below that is with the .ToList() method call, however it may also be as a result of foreach or any other method of iterating over the expression results.

Code example 1: Sequential processing of data with LINQ

class Program
{
    private static Random rnd = new Random();

    static void Main(string[] args)
    {
        DateTime start = DateTime.UtcNow;

        IEnumerable<int> items = Enumerable.Range(0, 20);

        var results = items
            .Select(ProcessItem)
            .ToList();

        results.ForEach(Console.WriteLine);

        DateTime end = DateTime.UtcNow;
        TimeSpan duration = end - start;

        Console.WriteLine("Finished. Took {0}", duration);

        Console.ReadLine();
    }

    private static string ProcessItem(int item)
    {
        // Simulate similar but slightly variable length processing
        int pause = rnd.Next(900, 1100);
        Thread.Sleep(pause);

        return string.Format("Result of item {0}", item);
    }
}

The output of the above code may look something like this:

Basic LINQ

As you can see this takes roughly of 20 seconds to process 20 items with each item taking about one second to process.

Code Example 2: Parallel processing of data with PLINQ

The AsParallel extension method can be found in the System.Linq namespace so no additional using statements are needed if you are already using LINQ.

class Program
{
    private static Random rnd = new Random();

    static void Main(string[] args)
    {
        DateTime start = DateTime.UtcNow;

        IEnumerable<int> items = Enumerable.Range(0, 20);

        var results = items.AsParallel()
            .Select(ProcessItem)
            .ToList();

        results.ForEach(Console.WriteLine);

        DateTime end = DateTime.UtcNow;
        TimeSpan duration = end - start;

        Console.WriteLine("Finished. Took {0}", duration);

        Console.ReadLine();
    }

    private static string ProcessItem(int item)
    {
        // Simulate similar but slightly variable length processing
        int pause = rnd.Next(900, 1100);
        Thread.Sleep(pause);

        return string.Format("Result of item {0}", item);
    }
}

The output of the above code may look something like this:

Basic PLINQ

The result of this code is that it takes roughly 5 second to process the 20 items. I have a 4 core processor so it would be in line with the expectation that the work is distributed across all 4 cores.

Parallelisation Talk Examples – Parallel.ForEach

These are some code examples from my introductory talk on Parallelisation. Showing the difference between a standard sequential foreach loop and its parallel equivalent.

Code example 1: Serial processing of a foreach loop

class Program
{
    private static Random rnd = new Random();

    static void Main(string[] args)
    {
        DateTime start = DateTime.UtcNow;

        IEnumerable items = Enumerable.Range(0,20);

        foreach(int item in items)
            ProcessLoop(item);

        DateTime end = DateTime.UtcNow;
        TimeSpan duration = end - start;

        Console.WriteLine("Finished. Took {0}", duration);

        Console.ReadLine();
    }

    private static void ProcessLoop(int item)
    {
        Console.WriteLine("Processing item {0}", item);

        // Simulate similar but slightly variable length processing
        int pause = rnd.Next(900, 1100);
        Thread.Sleep(pause);
    }
}

The output of the above code may look something like this:

Sequential foreach Example

As you can see this takes roughly of 20 seconds to process 20 items with each item taking about one second to process.

Code Example 2: Parallel processing of a foreach loop

The Parallel class can be found in the System.Threading.Tasks namespace.

class Program
{
    private static Random rnd = new Random();

    static void Main(string[] args)
    {
        DateTime start = DateTime.UtcNow;

        IEnumerable items = Enumerable.Range(0,20);

        Parallel.ForEach(items,
            (item) => ProcessLoop(item));

        DateTime end = DateTime.UtcNow;
        TimeSpan duration = end - start;

        Console.WriteLine("Finished. Took {0}", duration);

        Console.ReadLine();
    }

    private static void ProcessLoop(int item)
    {
        Console.WriteLine("Processing item {0}", item);

        // Simulate similar but slightly variable length processing
        int pause = rnd.Next(900, 1100);
        Thread.Sleep(pause);
    }
}

The output of the above code may look something like this:

Parallel.ForEach Example

The result of this code is that it takes roughly 5 second to process the 20 items. I have a 4 core processor so it would be in line with the expectation that the work is distributed across all 4 cores.