As we've seen in previous recipes, to create a task that can be cancelled, you pass in a cancellation token from a CancellationTokenSource
object. If you then make a call to the CancellationTokenSource.Cancel
method, the token signals all of the tasks that use it should terminate. The linked tasks detect this signal via the token and stop their activity in a safe manner.
Parallel loops support the same cancellation token mechanism as parallel tasks. In a parallel loop, you supply the CancellationToken
to the method in the ParallelOptions
parameter.
This recipe will download the contents of a book and split the words into a list of strings. We will then use a parallel loop to iterate through the words writing each to the console. However, we will create a separate task that sleeps for a few seconds and then calls the CancellationTokenSource.Cancel
method which will cancel the loop.
Let's create a Console Application in Visual Studio so that we can see how to break a loop. The steps are as follows:
BreakALoop
as the Solution name.using
directives to the top of your program class:using System; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks;
Main
method of the program class, let's create a CancellationTokenSource
and then add the CancellationToken
to a ParallelOptions
object.var tokenSource = new CancellationTokenSource(); var options = new ParallelOptions { CancellationToken = tokenSource.Token };
Cancel
method on the CancellationTokenSource
.Task.Factory.StartNew(() => { Thread.Sleep(new TimeSpan(0,0,5)); tokenSource.Cancel(); });
WebClient
to download the text of a book, and split the words from the book into a list of strings.char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '"', '(', ')', 'u000A' }; var client = new WebClient(); const string headerText = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; client.Headers.Add("user-agent", headerText); var words = client.DownloadString(@"http://www.gutenberg.org/files/2009/2009.txt"); var wordList = words.Split(delimiters, StringSplitOptions.RemoveEmptyEntries).Where(word => word.Length > 5).ToList(); wordList.Sort();
foreach
loop that writes the strings to the console. The loop should be in a try/catch and we should be catching OperationCancelledException
and AggregateException
.try { var loopResult = Parallel.ForEach(wordList, options, (currentWord, loopState) => Console.WriteLine(currentWord)); Console.WriteLine("Loop Completed : {0}", loopResult.IsCompleted.ToString()); } catch (OperationCanceledException) { Console.WriteLine("Operation Cancelled"); } catch (AggregateException) { Console.WriteLine("Operation Cancelled"); } Console.ReadLine();
In this recipe we are using another overload of the Parallel.ForEach
method that accepts an IEnumerable
source, a ParallelOptions
object, and an Action
delegate.
ForEach<TSource>(IEnumerable<TSource>, ParallelOptions, Action<TSource>)
The difference between cancelling a task and cancelling a parallel loop is how we pass in the CancellationToken
. With a task, a CancellationToken
is passed directly into the constructor of the task. For a parallel loop, we set the CancellationToken
property of a ParallelOptions
object with our CancellationToken
, and then pass the ParallelOptions
object into the parallel loop method.
If the token that signals the cancellation is the same token that is set on the ParallelOptions
instance, then the parallel loop will throw an OperationCanceledException
on cancellation. If a different token causes cancellation, the loop will throw an AggregateException
with an OperationCanceledException
as an InnerException
. Both should be handled in your catch
blocks.
18.222.210.166