2

I am running Parallel.For loop in 2 different ways:

  1. Not writing any async/await but just calling Task ReadSensorAsync(int):

    var watch = System.Diagnostics.Stopwatch.StartNew();
    Parallel.For(3, 38,
    index => {
        ReadSensorsAsync(index);
    });
    watch.Stop();
    Console.WriteLine("Parallel finished in {0} ms", watch.ElapsedMilliseconds);
    

    The timing shows 1ms

  2. Running with async/await

    var watch = System.Diagnostics.Stopwatch.StartNew();
    Parallel.For(3, 38,
    async index => {
        await ReadSensorsAsync(index);
    });
    watch.Stop();
    Console.WriteLine("Parallel finished in {0} ms", watch.ElapsedMilliseconds);
    

    This shows 2-5ms.

Question is why there is such difference and which way is proper to use.

Pablo
  • 28,133
  • 34
  • 125
  • 215
  • 5
    Do not call async methods without waiting for their results. That's fire and forget. Any exceptions thrown in the async method will not be caught, they'll just silently fail. – mason Jun 02 '16 at 15:16
  • @mason: yes, initially 3rd way was the one without async ReadSensor function, which of course blocks for longer time and not what I wanted. – Pablo Jun 02 '16 at 15:21
  • @M.kazemAkhgary First way is not better, it will swallow the exceptions. – mason Jun 02 '16 at 15:22
  • @mason: so the overhead which I have with `await`ing is probably exception handling routines? – Pablo Jun 02 '16 at 15:24
  • It's important to note that a difference of 1-4ms is not very much to go on. If you want to benchmark, you need to do more iterations. I suggest you look into a helpful library like BenchmarkDotNet, as shown on [Scott Hanselman's blog](http://www.hanselman.com/blog/BenchmarkingNETCode.aspx). – mason Jun 02 '16 at 15:24
  • No, the difference is that you're not waiting for the tasks to complete unless you await them. You're just kicking them off and moving on. – mason Jun 02 '16 at 15:25
  • http://stackoverflow.com/a/23139769/4767498 – M.kazem Akhgary Jun 02 '16 at 15:25
  • I've done about 30-40 iterations for each test – Pablo Jun 02 '16 at 15:25
  • If 30-40 iterations only result in a couple of milliseconds of difference, that's not enough to go on. You should do enough iterations to last multiple seconds. – mason Jun 02 '16 at 15:26
  • @mason: I am not waiting for tasks to complete either way, because behind both cases `Task.Run` is doing the job. – Pablo Jun 02 '16 at 15:26
  • @Pablo it's not overhead. When not awaiting for a task you essentially ignore every work done in the task, and move on. The 1 ms you're seeing is the time it takes to kick off the tasks, without ever waiting for them to finish. – Dbl Jun 02 '16 at 15:27
  • 1
    @Pablo if you're not awaiting them you should write to console "All tasks started" but not Parallel finished. What parallel.for is doing is still ongoing when you're getting that message. – Dbl Jun 02 '16 at 15:29
  • 3
    i think you should not use `Parallel.For` here based on that answer or this one http://stackoverflow.com/questions/19284202/how-to-correctly-write-parallel-for-with-async-methods – M.kazem Akhgary Jun 02 '16 at 15:29
  • @M.kazem Akhgary: I need to use `Parallel.For` to start all tasks at the same time. When they finish is not important, but they start together is important. Also I don't want the `Parallel.For` itself to block the calling thread. – Pablo Jun 02 '16 at 15:31
  • also Parallel is not designed for short tasks. i dont know how much your tasks last but i just wanted to mention it... from the timings it seems they are short. – M.kazem Akhgary Jun 02 '16 at 15:32
  • also read answer here http://stackoverflow.com/questions/4172705/should-i-always-use-parallel-foreach-because-more-threads-must-speed-up-everythi read jon skeets answer 3rd point – M.kazem Akhgary Jun 02 '16 at 15:33
  • 1
    @Pablo Parallel for does not start all tasks at the same time either. it just makes sure they are executed in parallel depending on core count. You might as well use await Task.WhenAll instead – Dbl Jun 02 '16 at 15:46
  • 1
    So you're using Parallel.For and then in the method you're calling Task.Run? That's actually creating 2x as many threads as you need. – Erik Funkenbusch Jun 02 '16 at 15:57

1 Answers1

5

First of all, neither version makes any sense. Parallel.For is for running CPU-bound (or possibly blocking IO-bound) operations. You're using it for starting asynchronous operations.

You're not waiting for the operation to complete, and you say that's intentional, but it's also dangerous: if an exception happens in ReadSensorsAsync, you have no way of catching it.

Since starting an async operation should be very fast, to start many of them at once, you don't need Parallel.For, you can use normal for:

for (int i = 3; i < 38; i++)
{
    ReadSensorsAsync(i);
}

(But again, I do no recommend ignoring the returned Task.)


As for timing, the big difference is probably because you're ignoring warmup: when you call ReadSensorsAsync for the first time, it has to be JIT compiled, which for such simple operations, will skew the results significantly.

Here are numbers from my machine, the format is "running for the first time"; "running for the second time":

  • calling ReadSensorsAsync once (for comparison): 7.6 ms; 0.04 ms
  • for: 7.5 ms; 0.05 ms
  • Parallel.For without await: 8.0 ms; 0.5 ms
  • Parallel.For with await: 11 ms; 2.6 ms

As you can see, using Parallel.For only adds overhead. And using it with await adds even more overhead, because starting an async method requires creating a state machine, which takes some time.

svick
  • 236,525
  • 50
  • 385
  • 514