ChatGPT解决这个技术问题 Extra ChatGPT

Parallel.ForEach vs Task.Run and Task.WhenAll

What are the differences between using Parallel.ForEach or Task.Run() to start a set of tasks asynchronously?

Version 1:

List<string> strings = new List<string> { "s1", "s2", "s3" };
Parallel.ForEach(strings, s =>
{
    DoSomething(s);
});

Version 2:

List<string> strings = new List<string> { "s1", "s2", "s3" };
List<Task> Tasks = new List<Task>();
foreach (var s in strings)
{
    Tasks.Add(Task.Run(() => DoSomething(s)));
}
await Task.WhenAll(Tasks);
I think the 2nd code fragment would be almost equal to the 1st one if you used Task.WaitAll instead of Task.WhenAll.
Please also note that the second one will perform DoSomething("s3") three times and it will not produce the same result! stackoverflow.com/questions/4684320/…
@Dan: note that Version 2 uses async/await, which means it's a different question. Async/await was introduced with VS 2012, 1.5 years after the possible duplicate thread was written.
@Nullius, since C#5, captured variables behave the expected way, and the loop above performs DoSomething for each of the three strings, see e.g. stackoverflow.com/questions/12112881/…. This question is obviously for C#5, as Task.WhenAll was introduced in C#5, with .NET Framework 4.5. So it is not correct that the second one will perform DoSomething("s3") three times.

R
Reed Copsey

In this case, the second method will asynchronously wait for the tasks to complete instead of blocking.

However, there is a disadvantage to use Task.Run in a loop- With Parallel.ForEach, there is a Partitioner which gets created to avoid making more tasks than necessary. Task.Run will always make a single task per item (since you're doing this), but the Parallel class batches work so you create fewer tasks than total work items. This can provide significantly better overall performance, especially if the loop body has a small amount of work per item.

If this is the case, you can combine both options by writing:

await Task.Run(() => Parallel.ForEach(strings, s =>
{
    DoSomething(s);
}));

Note that this can also be written in this shorter form:

await Task.Run(() => Parallel.ForEach(strings, DoSomething));

Great answer, I was wondering if you could point me to a good reading material on this topic ?
My Parallel.ForEach construct was crashing my application. I was performing some heavy image processing inside it. However, when i added Task.Run(()=> Parallel.ForEach(....)); It stopped crashing. Can you explain why? Please note i constrain the parallel options to the number of cores on system.
What if DoSomething is async void DoSomething?
What about async Task DoSomething?
@ShawnMclean - you can add async as:await Task.Run(() => Parallel.ForEach(strings, async s => { await DoSomething(s); }));
R
Rand Random

The first version will synchronously block the calling thread (and run some of the tasks on it). If it's a UI thread, this will freeze the UI.

The second version will run the tasks asynchronously in the thread pool and release the calling thread until they're done.

There are also differences in the scheduling algorithms used.

Note that your second example can be shortened to

await Task.WhenAll(strings.Select(s => Task.Run(() => DoSomething(s))));

shouldn't it be await Task.WhenAll(strings.Select(async s => await Task.Run(() => DoSomething(s)));? I had problems when returning tasks (instead of awaiting) especially when statements like using were involved to dispose objects.
My Parallel.ForEach call was causing my UI to crash I added Task.Run(()=> Parallel.ForEach (.... ) ); to it and it resolved crashing.
Does option 2 add extra overhead to the computer compared to option 1 for large number of tasks?
N
Nimantha

I have seen Parallel.ForEach used inappropriately, and I figured an example in this question would help.

When you run the code below in a Console app, you will see how the tasks executed in Parallel.ForEach doesn't block the calling thread. This could be okay if you don't care about the result (positive or negative) but if you do need the result, you should make sure to use 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}]");
        }
    }
}

Here is the result:

https://i.stack.imgur.com/N2sCb.png

Conclusion:

Using the Parallel.ForEach with a Task will not block the calling thread. If you care about the result, make sure to await the tasks.


I think this result is obvious, cause you start Async Method from ForEach Body (i.e. using new ThreadPool Thread without waiting for result). Here we must call DoSomethingAsync(i, prefix).Result.
@Mic While the result may seem obvious for you, the result of using Parallel.ForEach inappropriately in a web application can cause some serious issues within a server that will not appear until a load is put on the app. The post is not to say you shouldn't use it, but to make sure those who do use it know what is actually going to happen. Additionally, you should avoid using .Result as you should always be using async/await.
Parallel.ForEach cannot be used for async method calls. As DoSomething returns a Task which is not awaited, you should call .Wait() on it. Now you'll see that Parallel.ForEach returns only after all work is done.
@Bouke the point of the answer is to help those that are not aware of the differences. That said, you can use a task within Parallel.ForEach, but it will not be executed on the main thread. That does not mean you should, but it is allowable in the code as the example demonstrates. This means the code in the task is executing on a different thread and not blocked. There are scenarios where someone may want to have this occur, but they should be aware of what is happening.
C
Chris M.

I ended up doing this, as it felt easier to read:

  List<Task> x = new List<Task>();
  foreach(var s in myCollectionOfObject)
  {
      // Note there is no await here. Just collection the Tasks
      x.Add(s.DoSomethingAsync());
  }
  await Task.WhenAll(x);

This way you are doing the Tasks are being executed one after the other or the WhenAll are starting all of them at once?
As far as I can tell, they're all started when I call "DoSomethingAsync()". Nothing is blocking on them, however, until the WhenAll is called.
You mean when the first "DoSomethingAsync()" is called?
@ChrisM. It will be blocked until the first await of DoSomethingAsync() since this is what will transfer execution back to your loop. If it's synchronous and your return a Task, all of the code will be run one after another and the WhenAll will wait for all the Tasks to complete