Cancel tasks in Parallel.Invoke
Solution 1:
Here is my attempt to confirm your claim, that the Parallel.Invoke
method ignores the CancellationToken
passed to its options. I am creating a CancellationTokenSource
that is canceled with a timer after 700 msec, and 20 Action
s that each requires 500 msec to complete. Let's pass these 20 actions to Parallel.Invoke
, and see what happens:
static class Program
{
static void Main()
{
var cts = new CancellationTokenSource(700);
cts.Token.Register(() => Print($"The Token was canceled."));
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = 2,
CancellationToken = cts.Token
};
Print("Before starting the Parallel.Invoke.");
try
{
Parallel.Invoke(options, Enumerable.Range(1, 20).Select(i => new Action(() =>
{
Print($"Running action #{i}");
Thread.Sleep(500);
})).ToArray());
}
catch (OperationCanceledException)
{
Print("The Parallel.Invoke was canceled.");
}
}
static void Print(object value)
{
Console.WriteLine($@"{DateTime.Now:HH:mm:ss.fff} [{Thread.CurrentThread
.ManagedThreadId}] > {value}");
}
}
Output:
12:12:46.422 [1] > Before starting the Parallel.Invoke.
12:12:46.450 [1] > Running action #1
12:12:46.451 [5] > Running action #2
12:12:46.951 [1] > Running action #3
12:12:46.951 [5] > Running action #4
12:12:47.122 [7] > The Token was canceled.
12:12:47.458 [1] > The Parallel.Invoke was canceled.
Try it on Fiddle.
It seems that the CancellationToken
is respected. Only 4 of the 20 actions are executed, and no more actions are invoked after the cancellation of the token.
Notice however that I have configured the Parallel.Invoke
with a small MaxDegreeOfParallelism
. This is important. If instead I configure this option with a large value, like 100, or leave it to its default value which is -1 (unbounded), than all 20 actions will be invoked. What happens in this case is that the ThreadPool
is saturated, meaning that all the available ThreadPool
threads are borrowed aggressively by the Parallel.Invoke
method, and there is no available thread to do other things, like invoking the scheduled cancellation of the CancellationTokenSource
! So the cancellation is postponed until the completion of the Parallel.Invoke
, which is obviously too late.
The moral lesson is: always configure the MaxDegreeOfParallelism
option whenever you use the Parallel
class. Don't believe the documentation that says: "Generally, you do not need to modify this setting". This is a horrible advice. You should only follow this advice if your intention is to starve your ThreadPool
to death.