Introduction & Background
In this article, we are going to focus on the Parallel Class. However; before everything else, I assume that you are familiar with the basic looping constructs of the C# language. As we all know, these looping constructs such as while, do-while, for, for-each are one of the foundations of the language. Furthermore; these looping constructs are sequential loops by nature because an iteration isn’t started until the previous iteration has completed. I’m also guessing that you have at least abused these looping constructs sometime in your programming career because it shows your curiosity in learning the language.
Now, going back to Parallel loops. The Task Parallel Library (TPL) supports it. Even I was fascinated by it at first. Moreover; this library provides a common replacement to the sequential loops that we are familiar with and this article will focus on that.
Here is the list of topics of this article
- What is the classic fork/join model?
- What is a Parallel class?
- How to use Parallel. Invoke?
- How to use Parallel.For?
- What's ParallelLoopState and ParallelLoopResult?
What is Classic Fork/Join Model?
We can define the classic fork/join model by saying: “It is a way of configuring and executing your parallel programs, in such a way the executed one departs off in parallel and then merge at a certain point and gets back to sequential execution of your program.”. Wow! Jargon. For me just imagine that your program is running 10 methods in parallel (in any order) and at some point, these running methods need to join at a certain point so that your program will resume its default execution mode which is sequential. If ever I didn’t explain it well, you can go directly to Wikipedia using this link.
To see these in action, you can immediately go straight ahead to the code samples below.
What is a Parallel Class?
The parallel class resides within the namespace of System.Threading.Tasks and it provides support for parallel loops. Furthermore; it mimics the sequential loops that most developers are used to. And these are the Parallel. Invoke, Parallel. For and Parallel.ForEach.
How to use Parallel.Invoke?
Before jumping into the code sample, let’s begin to see what parameters are available for us to use.
- public static class Parallel
- {
- public static void Invoke(params Action[] actions);
- public static void Invoke(ParallelOptions parallelOptions, params Action[] actions);
- }
See the example below:
What is the ParallelOptions class?
Basically, this class helps developers to set the configuration which will influence the behavior of the methods inside the Parallel class. Furthermore; this class has three (3) properties which are CancellationToken, MaxDegreeOfParallelism, and TaskScheduler. However; we are just going to discuss the usage of CancellationToken and MaxDegreeOfParallelism only.
CancellationToken
It helps you as a developer to control the termination of the parallel invocation/execution. See the example below:
- /// <summary>
- /// In this example we are going to download the an html page from Microsoft page having this url 'https://www.microsoft.com/en-ph/'.
- /// We are going to count the number of span, paragraph and links in the page in parallel.
- /// When you debug this sample unit test you'll see that it behaves differently.
- /// Meaning it can run in order based on the delegate actions that we passed. In our case we only passed 3 Action delegates.
- /// However; will be cancelling the functionality.
- /// </summary>
- [Fact]
- public void Test_Parallel_Invoke_Passing_An_Action_Delegate_And_CancellationToken()
- {
- //Define the values needed when the parallel.invoke ended and joined with the main thread.
- int totalSpan = 0;
- int totalParagraph = 0;
- int totalLink = 0;
- //define the URL needed to count the following elements: span, paragraph, and link.
- Uri url = new Uri("https://www.microsoft.com/en-ph/");
- //Define the cancellation token source we need to cancel even before the parallel.invoke is executed
- CancellationTokenSource tokenSource = new CancellationTokenSource();
- CancellationToken token = tokenSource.Token;
- //Define the ParallelOptions class to be passed as an argument to the first parameter of Parallel.Invoke
- ParallelOptions options = new ParallelOptions { CancellationToken = token };
- Func<CancellationToken, string, string, Uri, int> getTotalElement = delegate (CancellationToken token, string message, string element, Uri url)
- {
- this._output.WriteLine(message);
- token.ThrowIfCancellationRequested();
- var client = new WebClient();
- var text = client.DownloadString(url.AbsoluteUri);
- var doc = new HtmlDocument();
- doc.LoadHtml(text);
- return doc.DocumentNode.SelectNodes(element).Count();
- };
- //you can uncomment this to see the full action
- tokenSource.Cancel(); //lets cancel
- try
- {
- Parallel.Invoke(options,
- () => totalSpan = getTotalElement(token, "started to get the span element", "//span", url),
- () => totalParagraph = getTotalElement(token, "started to get the paragraph element", "//p", url),
- () => totalLink = getTotalElement(token, "started to get and count total link element", "//link", url));
- }
- catch (OperationCanceledException operationCanceled)
- {
- this._output.WriteLine(operationCanceled.Message);
- }
- Assert.True(totalLink == 0);
- Assert.True(totalParagraph == 0);
- Assert.True(totalSpan == 0);
- }
- /// <summary>
- /// In this example we are going to throw and handle an exception from the Action delegates
- /// </summary>
- [Fact]
- public void Test_Parallel_Invoke_Passing_An_Action_And_HandleException()
- {
- //Define the values needed when the Parallel.Invoke ended and joined with the main thread.
- int totalSpan = 0;
- int totalParagraph = 0;
- int totalLink = 0;
- //define the URL needed to count the following elements: span, paragraph, and link.
- Uri url = new Uri("https://www.microsoft.com/en-ph/");
- /*We no longer finish the method to its proper functionality.
- * We just need to throw an exception and see how it can be handled.
- */
- Func<string, string, Uri, int> getTotalElement = delegate (string message, string element, Uri url)
- {
- throw new Exception($"Random exception from {element}");
- };
- #region prove-that-parallel-invoke-throws-an-aggregate-exception
- var exception = Assert.Throws<AggregateException>(() => {
- Parallel.Invoke(
- () => totalSpan = getTotalElement("started to get the span element", "//span", url),
- () => totalParagraph = getTotalElement("started to get the paragraph element", "//p", url),
- () => totalLink = getTotalElement("started to get and count total link element", "//link", url));
- });
- //let's assert if all messages are equal to 'Random exception'
- Assert.True(((AggregateException)exception).Flatten().InnerExceptions.All(x => x.Message.Contains("Random exception")));
- #endregion
- //in this section will try to handle the exception via the try-catch code block
- try
- {
- Parallel.Invoke(
- () => totalSpan = getTotalElement("started to get the span element", "//span", url),
- () => totalParagraph = getTotalElement("started to get the paragraph element", "//p", url),
- () => totalLink = getTotalElement("started to get and count total link element", "//link", url));
- }
- catch (AggregateException aggregateException)
- {
- foreach (Exception error in aggregateException.Flatten().InnerExceptions)
- {
- this._output.WriteLine(error.Message);
- }
- }
- //end of try-catch code block
- }
MaxDegreeOfParallelism
If you wish to limit the number of tasks that can be used for a given parallel invocation, and thus most likely influence the number of cores that will be used, see the example below:
- /// <summary>
- /// In this example we are going to download the HTML page from Microsoft page having this URL 'https://www.microsoft.com/en-ph/'.
- /// We are going to count the number of the span, paragraph, and links in the page in parallel.
- /// When you debug this sample unit test you'll see that it behaves differently.
- /// Meaning it can run in order based on the delegate actions that we passed. In our case, we only passed 4 Action delegates.
- /// However; we only need to invoke two methods at a time.
- /// </summary>
- [Fact]
- public void Test_Parallel_Invoke_Passing_An_Action_Delegate_And_MaxDegreeOfParallelism()
- {
- //Define the values needed when the parallel.invoke ended and joined with the main thread.
- int totalSpan = 0;
- int totalParagraph = 0;
- int totalLink = 0;
- int totalDiv = 0;
- //define the URL needed to count the following elements: span, paragraph, and link.
- Uri url = new Uri("https://www.microsoft.com/en-ph/");
- //Define the ParallelOptions class to be passed as an argument to the first parameter of Parallel.Invoke.
- //Then set the MaxDegreeOfParllelism to two (2)
- ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 2 };
- Func<string, string, Uri, int> getTotalElement = delegate (string message, string element, Uri url)
- {
- this._output.WriteLine($"{message} and Task Id: {Task.CurrentId}");
- var client = new WebClient();
- var text = client.DownloadString(url.AbsoluteUri);
- var doc = new HtmlDocument();
- doc.LoadHtml(text);
- return doc.DocumentNode.SelectNodes(element).Count();
- };
- try
- {
- Parallel.Invoke(options,
- () => totalSpan = getTotalElement("started to get the span element", "//span", url),
- () => totalParagraph = getTotalElement("started to get the paragraph element", "//p", url),
- () => totalLink = getTotalElement("started to get and count total link element", "//link", url),
- () => totalDiv = getTotalElement("started to get and count div element", "//div", url));
- }
- catch (OperationCanceledException operationCanceled)
- {
- this._output.WriteLine(operationCanceled.Message);
- }
- catch(AggregateException aggregateException)
- {
- foreach (Exception error in aggregateException.Flatten().InnerExceptions)
- {
- this._output.WriteLine(error.Message);
- }
- }
- Assert.True(totalLink > 0);
- Assert.True(totalParagraph > 0);
- Assert.True(totalSpan > 0);
- Assert.True(totalDiv > 0);
- }
How to use Parallel.For?
When you want to iterate through a sequence of numbers, for example, 1 to 30 in any order then we can use the Parallel.For. Just remember that the simple Parallel.For takes the start and end values of the loop, along with the Action<int> delegate which represents the body of the loop. See some examples below:

Join the conversation! Your thoughts help the community grow.