0

I am trying to learn about asynchronous programming and how I can benefit from it.

My hope is that I can use it to improve performance whenever I'm looping over a method that takes an significant time to complete, like the following method.

string AddStrings() { string result = ""; for (int i = 0; i < 10000; i++) { result += "hi"; } return result; } 

Obviously this method doesn't have much value, and I purposely made it ineffecient, in order to test the benefits of asynchronous programming. The test is done by looping over the method 100 times, first synchronously and then asynchronously.

Stopwatch watch = new Stopwatch(); watch.Start(); List<string> results = WorkSync(); //List<string> results = await WorkAsyncParallel(); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds); List<string> WorkSync() { var stringList = new List<string>(); for (int i = 0; i < 100; i++) { stringList.Add(AddStrings()); } return stringList; } async Task<List<string>> WorkAsyncParallel() { var taskList = new List<Task<string>>(); for (int i = 0; i < 100; i++) { taskList.Add(Task.Run(() => AddStrings())); } var results = (await Task.WhenAll(taskList)).ToList(); return results; } 

Super optimistically (naively), I was hoping that the asynchronous loop would be 100 times as fast as the synchronous loop, as all the tasks are running at the same time. While that didn't exactly turn out to be the case, the time of the loop was decreased by more than two thirds, from around 5000 miliseconds to 1500 miliseconds!

Now my questions are:

  1. What makes the asynchronous loop faster than the synchronous loop, but not nearly 100 times faster? I'm guessing each of the 100 tasks are fighthing for a limited amount of CPU?
  2. Is this a valid method to improve performance when looping methods?

Thank you in advance.

3
  • You got a 70% improvement in performance time, so I'd say this is definitely worth it. Your AddStrings method could be improved as you are using string concatenation, you could modify it to use a StringBuilder Commented May 26, 2022 at 14:18
  • 3
    Parallelizing synchronous work this way is possible, but not really the intended use case of async/await. For that things like Parallel.ForEach, parallel LINQ (Enumerable.AsParallel) or the TPL Dataflow library are more suitable, which are easier than Task.Run (the first two, in any case) and potentially have less overhead (as no Tasks need to be allocated, even though this is a light-weight operation). A 100-fold speedup of purely synchronous work is possible only if your machine actually has 100 processors. Commented May 26, 2022 at 14:30
  • Thank you very much Jeroen, I will be looking into the tools that you have recommended! Commented May 27, 2022 at 9:05

1 Answer 1

4

My hope is that I can use it to improve performance

Not really. Concurrency (parallel or asynchronous) can improve performance, but asynchronous code on its own is more about freeing up threads. Asynchronous code provides two main benefits:

  1. Server-side apps get better scalability. By using fewer threads, asynchronous code can scale further and faster than synchronous code.
  2. UI apps get better responsiveness. By freeing up the UI thread, asynchronous code provides a better user experience.

Neither of these have much to do with performance. E.g., when comparing a synchronous server-side request handler with its basic asynchronous counterpart, the asynchronous one is usually slower (slightly), but the server as a whole scales better.

That said, asynchronous code can enable natural concurrency (e.g., Task.WhenAll). And if you do end up adding concurrency, then you can see some performance benefits - sometimes quite drastic ones.

The test is done by looping over the method 100 times, first synchronously and then asynchronously.

Technically, you're comparing single-threaded and parallel code, here.

As Jeroen pointed out in the comments, Parallel or PLINQ are the proper tools to use if you have a parallel problem. While you can use Task.Run, it's a very low-level tool for parallel programming. Task.Run is more commonly used for "shove this one thing to the thread pool", not "shove these 100 things to the thread pool".

What makes the asynchronous loop faster than the synchronous loop, but not nearly 100 times faster? I'm guessing each of the 100 tasks are fighthing for a limited amount of CPU?

Yes. Most machines these days have multi-core CPUs, and each core can do one thing at a time. So if you have 4 or 8 cores, that's the limit of your parallelism.

Is this a valid method to improve performance when looping methods?

If you have a lot of work to do in parallel, then parallel programming is acceptable. Again, I recommend using higher-level constructs like Parallel or PLINQ, which have better built-in partitioning strategies and other optimizations, which will be more efficient than throwing a bunch of tasks at the thread pool.

Parallel programming does have its caveats:

  • If your work is too fine-grained, then parallel work can end up being slower, as the overhead of partitioning, queueing, and scheduling erases the gains from concurrency.
  • You generally want to avoid parallelism in some situations such as handling a request on the server side. It's just usually not a good idea to allow one request to consume all the CPU resources of the whole server.

Now, if you want to test out asynchronous benefits, then I recommend using an operation that is inherently asynchronous (e.g., a client web request). Say, if your code was hitting 100 URLs. That would be something more naturally asynchronous that doesn't require thread pool threads.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you Stephen for a brilliant explanation, it really helps my understanding of the topic, and what areas to investigate to gain further knowledge. It makes sense that web requests would benefit from being asynchronous. I will definitely look into the parallel tools that you and Jeroen recommended.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.