Is Task.Run considered bad practice in an ASP .NET MVC Web Application?

24,247

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.

Share:
24,247
Fabe
Author by

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, 2022

Comments

  • Fabe
    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
      kirotab over 8 years
      Task.Run is not creating a thread but is using Thread Pool I think.
    • Fabe
      Fabe over 8 years
      Ah you are absolutely right! Thanks for the correction.
  • Fabe
    Fabe over 8 years
    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
    Fabe over 8 years
    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
    Fabe over 8 years
    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
    Fabe over 8 years
    But wouldn't it be better to just use Task.FromResult?
  • i3arnon
    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
    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
    Stephen Cleary over 8 years
    @Fabe: No. Just drop CalculateAsync completely and use CalculateSync instead.
  • Fabe
    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
    Fabe over 8 years
    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?
  • i3arnon
    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
    Avinash Jain over 8 years
    Task<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
    i3arnon over 8 years
    @Fabe Just to clarify.. no thread is created (or "spawned") when using Task.Run without the LongRunning flag.
  • Fabe
    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
    The Muffin Man over 7 years
    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.
  • Don Cheadle
    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
    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
    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
    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
    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
    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
    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
    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
    Chris Pratt almost 6 years
    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.
  • erhan355
    erhan355 about 5 years
    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 ?