62

Background

We are currently developing a web application, which relies on ASP .NET MVC 5, Angular.JS 1.4, Web API 2 and Entity Framework 6. For scalability reasons, the web application heavility relies on the async/await pattern. Our domain requires some cpu-intensive calculations, which can takes some seconds (<10s). In the past some team members used Task.Run, in order to speed up the calculations.Since starting an extra thread inside ASP .NET MVC or Web API controllers is considered a bad practise (the thread is not known by the IIS, so not considered on AppDomain Recycle => See Stephen Cleary's blog post), they used ConfigureAwait(false).

Example

public async Task CalculateAsync(double param1, double param2)
{
    // CalculateSync is synchronous and cpu-intensive (<10s)
    await Task.Run(() => this.CalculateSync(param1, param2))).ConfigureAwait(false);
}

Questions

  • Is there any performance benefit in using Task.Run in an async Web API Controller for cpu-bound operations?
  • Does ConfigureAwait(false) really avoid the creation of an extra thread?
i3arnon
  • 113,022
  • 33
  • 324
  • 344
Fabe
  • 1,185
  • 4
  • 11
  • 22

4 Answers4

81

Is there any performance benefit in using Task.Run in an async Web API Controller for cpu-bound operations?

Zero. None. In fact, you're hindering performance by spawning a new thread. Within the context of a web application, spawning a thread is not the same thing as running in the "background". This is due to the nature of a web request. When there's an incoming request, a thread is taken from the pool to service the request. Using async allows the thread to be returned before the end of the request, if and only if the thread is in a wait-state, i.e. idle. Spawning a thread to do work on, effectively idles the primary thread, allowing it to be returned to the pool, but you've still got an active thread. Returning the original thread to the pool does nothing at that point. Then, when the new thread finishes its work, you've got to request a main thread back from the pool, and finally return the response. The response cannot be returned until all work has completed, so whether you use 1 thread or a hundred, async or sync, the response cannot be returned until everything finishes. Therefore, using additional threads does nothing but add overhead.

Does ConfigureAwait(false) really avoid the creation of an extra thread?

No, or more appropriately, it's not about that. ConfigureAwait is just an optimization hint, and only determines whether the original context is maintained between thread jumps. Long and short, it has nothing to do with the creation of a thread, and at least in the context of an ASP.NET application, has negligible performance impact either way.

user1306322
  • 8,561
  • 18
  • 61
  • 122
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Ah many thanks for the detailed explanation! So it is just better to just use Task.FromResult to invoke the synchronous method "CalculateSync" from the async method "CalculateAsync" and block the thread for <10s. Right? – Fabe Nov 17 '15 at 20:31
  • 3
    @Fabe: No. Just drop `CalculateAsync` completely and use `CalculateSync` instead. – Stephen Cleary Nov 17 '15 at 22:08
  • @Stephen Cleary: But if I have to provide an async method signature, i.e. due to an interface, should I use Task.FromResult to provide it? I am thinking of scenarios where some implementations of an interface a real asynchronous ones and some are not. – Fabe Nov 17 '15 at 22:55
  • 2
    @Fabe Just to clarify.. no thread is created (or "spawned") when using `Task.Run` without the `LongRunning` flag. – i3arnon Nov 18 '15 at 08:11
  • 1
    @i3arnon: Many thanks! This flag is new to me, have to look it up in MSDN. If I could, I would both yours and Chriss Pratts answer as most helpful ;-) – Fabe Nov 18 '15 at 08:22
  • I think what's missing from nearly 100% of answers about Task.Run is that while scalability and performance will degrade, you can use it to increase responsiveness depending on the problem. If you've got a special use case where you know how many uses you have and you've got beefy hardware on your machines I think it can be appropriate to use Task.Run in a web app. Also you may have operations that don't need to wait for a response, a fire and forget. – The Muffin Man Dec 07 '16 at 16:24
  • @Chris Pratt, I fully understand your points, however, why are there other articles written that state the complete oposite?: https://www.itworld.com/article/2700230/development/why-you-should-use-async-tasks-in--net-4-5-and-entity-framework-6.html – Devedse Aug 10 '18 at 14:28
  • I'm not sure you understood the question or even my answer. There's nothing in the linked article that contradicts anything here. We're talking about using `Task.Run`, not using async, in general. – Chris Pratt Aug 10 '18 at 14:33
  • 1
    I didn't get this part of answer."Using async allows the thread to be returned before the end of the request, if and only if the thread is in a wait-state, i.e. idle.".As far as I understand main threads return to application pool but because it has started a new thread,it has to wait until that thread finishes its job.So until all threads spawned by our main finishes job,main thread can't serve for new requests.Is it true ? – erhan355 Mar 01 '19 at 11:14
  • ChrisPratt I'm also confused about what you mentioned (see @erhan355's comment above). Could you please elaborate? – David Klempfner Sep 29 '22 at 07:12
  • What if you had 3 different intensive CPU bound methods you wanted to run in parallel. Couldn't you use `Task.WhenAll(task1, task2, task3)`? Wouldn't this result in a response being sent to the client sooner, at the expense of having fewer threads in the threadpool available to pick up new HTTP requests? – David Klempfner Sep 29 '22 at 07:35
27

Is there any performance benefit in using Task.Run in an async Web API Controller for cpu-bound operations?

No. And it doesn't matter whether it's CPU bound or not.

Task.Run offloads work to a ThreadPool thread. The web api request already uses a ThreadPool thread so you're just limiting scalability by offloading to another thread with no reason.

This is useful in UI apps, where the UI thread is a special single thread.

Does ConfigureAwait(false) really avoid the creation of an extra thread?

It doesn't affect thread creating in one way or another. All it does is configures whether to resume on the captured SynchronizationContext or not.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • Many thanks! Same question as to Chris Pratt: So it is just better to just use Task.FromResult to invoke the synchronous method "CalculateSync" from the async method "CalculateAsync" and block the thread for <10s. Right? – Fabe Nov 17 '15 at 20:36
  • 5
    @Fabe just call the synchronous method directly. If there's no asynchronous operation you don't need a task to begin with. – i3arnon Nov 17 '15 at 20:47
  • 3
    @Fabe CalculateAsync is pointless and shouldn't exist. If you need to offload CalculateSync to a different thread let the consumer use Task.Run themselves. – i3arnon Nov 17 '15 at 20:55
  • Ok but if the consumer is a web api controller hosted in IIS and called by angular, i should not use Task.Run there, right? – Fabe Nov 17 '15 at 22:57
  • 1
    @Fabe No. You only want to offload to a different thread when you have a "special" thread (e.g. a UI app). Web API doesn't have that. – i3arnon Nov 17 '15 at 23:25
  • @i3arnon disagree with `doesn't matter whether it's cpu bound or not`. If the controller body calls to a webservice, and that call takes 10 seconds to respond, then the thread used to execute the Controller action is now doing nothing for 10 seconds. That would be a good reason to await and let the thread go, and queue up some other probably quicker controller action. Seems like if it's an IO-bound wait it could make sense to use a Task and await the controller result... – Don Cheadle Jun 26 '18 at 02:11
  • @i3arnon hm but right... the Task.Run ITSELF is going to pull from the Thread Pool anyway... so at some point that thread would be waiting 10 seconds! I.e. even for IO bound, it would be like cutting the request thread short, but then just starting another one... Am I understanding your answer? – Don Cheadle Jun 26 '18 at 02:18
  • @mmcrae Using `await Task.Run(...)` in the controller takes work *from* a thread-pool thread and moves it to another thread-pool thread. It's pointless in that case because both threads are the same and are taken from the thread-pool. – i3arnon Jun 28 '18 at 16:20
  • @mmcrae It doesn't matter what kind of work that thread is doing because it's the same work the other one is now going to do. – i3arnon Jun 28 '18 at 16:21
  • @i3arnon I can imagine maybe a decent case for Task.Run in a controller? --> If it's a special Controller method and it has to do a long API request to a different system, and also some other computation. In that case... it seems reasonable to put that other API request in its own Task while the controller action does some work. Then at the end of the method wait/getthat long API call's results. So in this case, it does work while the Task.Run is calling an API. So that doesn't seem useless to me. – Don Cheadle Jun 29 '18 at 00:31
  • @mmcrae I intentionally wrote `await Task.Run` in that comment (to match the question). In that case the "controller" thread is done and goes back to the thread pool. That case is indeed pointless. – i3arnon Jun 29 '18 at 08:46
  • @mmcrae you can of course utilize parallelism and split up your workload over more threads, but that means you limit the number of request you can serve concurrently (and isn't what is actually being asked). – i3arnon Jun 29 '18 at 08:49
14

Is there any performance benefit in using Task.Run in an async Web API Controller for cpu-bound operations?

Think about what really happens - Task.Run() creates a Task on the thread pool, and your await operator will free the thread (I'm assuming all methods down the stack are also awaiting). Now your thread is back to the pool, and it might pick up that same Task! In this scenario, there is obviously no performance gain. There is performance hit, actually. But if the thread picks up another Task (that's probably what will happen), another thread would have to pick up CalculateSync() Task and continue from where the former stopped. It would have made more sense to let the original thread execute CalculateSync() in the first place, no Tasks involved, and let the other thread have the other queued Tasks.

Does ConfigureAwait(false) really avoid the creation of an extra thread?

Not at all. It merely points out that the continuation shouldn't be executed on the caller's context.

shay__
  • 3,815
  • 17
  • 34
  • Many thanks! Same question as to Chris Pratt and i3arnon: So it is just better to just use Task.FromResult to invoke the synchronous method "CalculateSync" from the async method "CalculateAsync" and block the thread for <10s. Right? – Fabe Nov 17 '15 at 20:37
2

There is one more thing that you need to consider. As you told your your api is doing CPU intensive task then async/await help to run the process in another thread and free your app pool for another request. Means your web api can handle more number of request per second if you use async/await correctly.

In your case look like this.CalculateSync(param1, param2) is non-async method so to call this asynchronously you have to use Task.Run.

I'll recommend to remove .ConfigureAwait(false) as this will actually decrease your performance.

Avinash Jain
  • 7,200
  • 2
  • 26
  • 40
  • But wouldn't it be better to just use Task.FromResult? – Fabe Nov 17 '15 at 20:38
  • 1
    Task is implicitly convertable to Task, so just to get a completed Task (with any T and any value) and use that. You can use Task.FromResult – Avinash Jain Nov 18 '15 at 07:55
  • Why would .ConfigureAwait(false) decrease performance? Wouldn't it increase performance since it prevents a useless synchronization context switch back to the original context? – David Klempfner Sep 29 '22 at 07:17