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 Actions 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.