Is Task.Run considered bad practice in an ASP .NET MVC Web Application?
Solution 1
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.
Solution 2
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.
Solution 3
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.
Solution 4
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.
Fabe
Working in software development since 2006 and specialised in solution architecture based on the Microsoft .NET platform. I aim to design better, faster and cheaper solutions based on platforms and to lead development teams to strive for customer satisfaction.
Updated on July 09, 2022Comments
-
Fabe almost 2 years
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?
-
kirotab over 8 yearsTask.Run is not creating a thread but is using Thread Pool I think.
-
Fabe over 8 yearsAh you are absolutely right! Thanks for the correction.
-
Fabe over 8 yearsAh 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 over 8 yearsMany 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 over 8 yearsMany 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 over 8 yearsBut wouldn't it be better to just use Task.FromResult?
-
i3arnon over 8 years@Fabe just call the synchronous method directly. If there's no asynchronous operation you don't need a task to begin with.
-
i3arnon over 8 years@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.
-
Stephen Cleary over 8 years@Fabe: No. Just drop
CalculateAsync
completely and useCalculateSync
instead. -
Fabe over 8 years@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 over 8 yearsOk but if the consumer is a web api controller hosted in IIS and called by angular, i should not use Task.Run there, right?
-
i3arnon over 8 years@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.
-
Avinash Jain over 8 yearsTask<T> is implicitly convertable to Task, so just to get a completed Task<T> (with any T and any value) and use that. You can use Task.FromResult
-
i3arnon over 8 years@Fabe Just to clarify.. no thread is created (or "spawned") when using
Task.Run
without theLongRunning
flag. -
Fabe over 8 years@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 ;-)
-
The Muffin Man over 7 yearsI 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.
-
Don Cheadle almost 6 years@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 almost 6 years@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?
-
i3arnon almost 6 years@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 almost 6 years@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.
-
Don Cheadle almost 6 years@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.
-
i3arnon almost 6 years@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 almost 6 years@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).
-
Devedse almost 6 years@Chris Pratt, I fully understand your points, however, why are there other articles written that state the complete oposite?: itworld.com/article/2700230/development/…
-
Chris Pratt almost 6 yearsI'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. -
erhan355 about 5 yearsI 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 ?