我已经看到 Parallel.ForEach 使用不当,我认为这个问题中的一个例子会有所帮助。
当您在控制台应用程序中运行以下代码时,您将看到在 Parallel.ForEach 中执行的任务如何不会阻塞调用线程。如果您不关心结果(正面或负面),这可能没问题,但如果您确实需要结果,则应确保使用 Task.WhenAll。
using System;
using System.Linq;
using System.Threading.Tasks;
namespace ParrellelEachExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var indexes = new int[] { 1, 2, 3 };
            RunExample((prefix) => Parallel.ForEach(indexes, (i) => DoSomethingAsync(i, prefix)),
                "Parallel.Foreach");
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("*You'll notice the tasks haven't run yet, because the main thread was not blocked*");
            Console.WriteLine("Press any key to start the next example...");
            Console.ReadKey();
            
            RunExample((prefix) => Task.WhenAll(indexes.Select(i => DoSomethingAsync(i, prefix)).ToArray()).Wait(),
                "Task.WhenAll");
            Console.WriteLine("All tasks are done.  Press any key to close...");
            Console.ReadKey();
        }
        static void RunExample(Action<string> action, string prefix)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine($"{Environment.NewLine}Starting '{prefix}'...");
            action(prefix);
            Console.WriteLine($"{Environment.NewLine}Finished '{prefix}'{Environment.NewLine}");
        }
        
        static async Task DoSomethingAsync(int i, string prefix)
        {
            await Task.Delay(i * 1000);
            Console.WriteLine($"Finished: {prefix}[{i}]");
        }
    }
}
结果如下:

结论:
将 Parallel.ForEach 与 Task 一起使用不会阻塞调用线程。如果您关心结果,请确保等待任务。