Task.WaitAll method vs Parallel.Invoke method

2019-02-05 16:12发布

I have sample code to compare processing time for Parallel approach and Task approach. The goal of this experiment is understanding of how do they work.

So my questions are:

  1. Why Parallel worked faster then Task?
  2. Do my results mean that I should use Parallel instead of Task?
  3. Where should I use Task and where Parallel?
  4. What benefits of using Task in comparison to Parallel?
  5. Does Task is just a wrap for ThreadPool.QueueUserWorkItem method?

        public Task SomeLongOperation()
        {
            return Task.Delay(3000);
        }
    
        static void Main(string[] args)
        {
            Program p = new Program();
            List<Task> tasks = new List<Task>();
    
            tasks.Add(Task.Factory.StartNew(() => p.SomeLongOperation()));
            tasks.Add(Task.Factory.StartNew(() => p.SomeLongOperation()));
    
            var arr = tasks.ToArray();
    
            Stopwatch sw = Stopwatch.StartNew();
            Task.WaitAll(arr);
            Console.WriteLine("Task wait all results: " + sw.Elapsed);
            sw.Stop();
    
            sw = Stopwatch.StartNew();
            Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation());
            Console.WriteLine("Parallel invoke results: " + sw.Elapsed);
            sw.Stop();
    
            Console.ReadKey();
        }
    

Here are my processing results: results

EDIT:

Changed code to look like this:

    Program p = new Program();
    Task[] tasks = new Task[2];

    Stopwatch sw = Stopwatch.StartNew();
    tasks[0] = Task.Factory.StartNew(() => p.SomeLongOperation());
    tasks[1] = Task.Factory.StartNew(() => p.SomeLongOperation());

    Task.WaitAll(tasks);
    Console.WriteLine("Task wait all results: " + sw.Elapsed);
    sw.Stop();

    sw = Stopwatch.StartNew();
    Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation());
    Console.WriteLine("Parallel invoke results: " + sw.Elapsed);
    sw.Stop();

My new results:

new results

EDIT 2: When I replaced code with Parallel.Invoke to be first and Task.WaitAll to be second the situation has been changed cardinally. Now Parallel is slower. It makes me think of incorrectness of my estimates. I changed code to look like this:

Program p = new Program();
Task[] tasks = new Task[2];

Stopwatch sw = null;
for (int i = 0; i < 10; i++)
{
    sw = Stopwatch.StartNew();
    Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation());
    string res = sw.Elapsed.ToString();
    Console.WriteLine("Parallel invoke results: " + res);
    sw.Stop();
}

for (int i = 0; i < 10; i++)
{
    sw = Stopwatch.StartNew();
    tasks[0] = Task.Factory.StartNew(() => p.SomeLongOperation());
    tasks[1] = Task.Factory.StartNew(() => p.SomeLongOperation());
    Task.WaitAll(tasks);
    string res2 = sw.Elapsed.ToString();
    Console.WriteLine("Task wait all results: " + res2);
    sw.Stop();
}

And here are my new results:

enter image description here

enter image description here

Now I can suggest that this experiment is much more clear. The results are almost the same. Sometimes Parallel and sometimes Task is faster. Now my questions are:

1. Where should I use Task and where Parallel?

2. What benefits of using Task in comparison to Parallel?

3. Does Task is just a wrap for ThreadPool.QueueUserWorkItem method?

Any helpful info that can clarify those questions are welcome.

2条回答
萌系小妹纸
2楼-- · 2019-02-05 16:55

EDIT as of this article from MSDN:

Both Parallel and Task are wrappers for ThreadPool. Parallel invoke also awaits until all tasks will be finished.

Related to your questions:

Using Task, Parallel or ThreadPool depends on the granularity of control you need to have on the execution of your parallel tasks. I'm personally got used to Task.Factory.StartNew(), but that's a personal opinion. The same relates to ThreadPool.QueueUserWorkItem()

Additional Information: The first call to Parallel.Invoke() and Task.Factory.StartNew() might be slower due to internal initialization.

查看更多
甜甜的少女心
3楼-- · 2019-02-05 16:58

If you start nongeneric Tasks(i.e. "void Tasks without a return value") and immediately Wait for them, use Parallel.Invoke instead. Your intent is immediately clear to the reader.

Use Tasks if:

  • you do not Wait immediately
  • you need return values
  • you need to give parameters to the methods called
  • you require TaskCreationOptions functionality
  • you require CancellationToken or TaskScheduler functionality and don't want to use ParallelOptions
  • basically, if you want more options or control

Yes, you can get around some of these, e.g. Parallel.Invoke(() => p.OpWithToken(CancellationToken) but that obfuscates your intent. Parallel.Invoke is for doing a bunch of work using as much CPU power as possible. It gets done, it doesn't deadlock, and you know this in advance.


Your testing is horrid though. The red flag would be that your long action is to wait 3000 milliseconds, yet your tests take less than a tenth of a millisecond.

Task.Factory.StartNew(() => p.SomeLongOperation());

StartNew takes an Action, and executes this in a new main Task. The action () => SomeLongOperation() creates a subtask Task. After this subtask is created (not completed), the call to SomeLongOperation() returns, and the Action is done. So the main Task is already completed after a tenth millisecond, while the two subtasks you have no reference to are still running in the background. The Parallel path also creates two subtasks, which it doesn't track at all, and returns.

The correct way would be tasks[0] = p.SomeLongOperation();, which assigns a running task to the array. Then WaitAll checks for the finishing of this task.

查看更多
登录 后发表回答