4

I have this ASP MVC call which is aync because of an async call it needs to make. I noticed there are a couple of slow sync calls (e.g. db access). The method needs to have all returned data available in order to proceed.

I thought of wrapping the sync calls with Task.Run and await for all of them.

Does it make sense to wrap the slow sync calls? What if there was only sync calls?

Igor Gatis
  • 4,648
  • 10
  • 43
  • 66

3 Answers3

6

Don't use Task.Run to parallelize work in the server-side code, unless the number of client requests you expect to serve concurrently is really low. Otherwise, you might speed up the processing of an individual request, but you'll hurt scalability of your web app for when there are many users.

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Isn't the point of using async/await to allow the current thread to be saved instead of waiting to process the request? If parallel tasks are needed, then he should be using the TPL, but using TAP should help with scalability instead of hindering it. – Ryan Peters Jun 19 '14 at 11:45
  • 1
    @RyanPeters, it is indeed, but in the server-side context you usually use TAP only for naturally asynchronous APIs (check Stephen Cleary's ["There Is No Thread"](http://blog.stephencleary.com/2013/11/there-is-no-thread.html)). This way, the initial thread of the HTTP request is released to the pool and then can serve another HTTP request. If however you use `Task.Run`, you don't release the thread. As to wrapping a single synchronous call, it doesn't make sense at all - you might as well do it on the current thread. As to wrapping multiple calls in parallel - you hurt scalability. – noseratio Jun 19 '14 at 12:09
  • 1
    Things may be different for a client-side UI app, where you don't care about scalability but rather do about keeping the UI thread responsive. – noseratio Jun 19 '14 at 12:10
  • @Noseratio - You only hurt scalability in the sense that you're consuming and blocking a thread pool thread, you're not taking extra CPU resources, and probably minimal memory (the threadpool is already allocated, so it's stack is already allocated). While this might indeed be an issue for scalability, it really depends on a lot of factors as to whether or not it's a problem. I agree, it's probably best to avoid it if possible, from a purity standpoint if nothing else. – Erik Funkenbusch Jun 19 '14 at 16:07
  • @ErikFunkenbusch, true, but just consuming (with `Task.Run`) and blocking a pool thread may have a very serious impact on scalability. There is also infamous `ThreadPool` stuttering behavior to keep in mind, here is a [related post](http://stackoverflow.com/a/23262585/1768303) with some research. After all, why speed up a request processing for a single client (by forking blocking calls with `Task.Run`), yet make other clients wait because there is no vacant pool threads? – noseratio Jun 19 '14 at 22:57
  • Yes, as I said, it depends on a lot of factors whether it will be a problem or not. Parallelizing tasks can mean a significant difference in performance for an end-user, but it must be balanced with the potential impact on the system. Many things can cause scalability issues, yet we still use them, so long as it's a conscious choice what tradeoffs you're making. – Erik Funkenbusch Jun 19 '14 at 23:40
3

To try and extend @Noseratio, spinning up thread to "speed up" sync work scales terribly bad.

An important thing to remember is that using Task.Run inside ASP.NET is extremely dangerous because the runtime isn't aware you queued work which needs to be done and IIS may attempt to recycle you app from time to time which will cause work to abrupt unintentionally.

If you're using .NET Framework 4.5.2 there is a solution via HostingEnvironment.QueueBackgroundWorkItem. You can read more about it in Fire and Forget on ASP.NET. If not, read Returning Early from ASP.NET Requests for a custom implementation. Both of these excellent articles are by @StephanCleary

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 1
    I don't think the author is talking about returning early here. You're talking about fire and forget, or async void methods, which can cause the problem you're talking about. In this case, he needs the results of the work thus he has to wait for them to finish, the worker process won't get recycled. So there's no "danger" to using Task.Run this way, although as @Noseratio mentions it's not great for scalability. – Erik Funkenbusch Jun 19 '14 at 15:51
  • He's awaiting those tasks, there is no reason why IIS wouldn't attempt to recycle eveb though he's `awaiting`. Im not talking about async void or fire and forget, im talking about the use of `Task.Run` in general inside ASP.NET – Yuval Itzchakov Jun 19 '14 at 16:19
  • 1
    If the primary action thread has not completed and is waiting, it's not going to recycle unless it times out. If you're not talking about Fire and Forget, why did you reference the two articles about it? – Erik Funkenbusch Jun 19 '14 at 16:51
  • I think @ErikFunkenbusch has a point here. I.e., the primary action thread (the initial thread the HTTP request has landed on to be handled) will be released when the execution point hits the first `await` inside an `async` Web API method. However, the request is still pending at this point (because of the `await`), so the IIS process/appdomain won't be recycled. Internally, the tracking [is done via `AsyncManager`](http://stackoverflow.com/a/21981799/1768303). However, a thing like `await Task.Run(DoWork)` doesn't make sense in ASP.NET: it just releases one thread and acquires another. – noseratio Jun 20 '14 at 02:11
1

If the tasks are independent (don't depend on data from the other tasks), and can be performed independently, then yes, by all means execute them asynchronously. If they're Entity Framework, and it's version 6 then it offers Async methods to call, and you don't have to wrap them in Task.Run.

Even if the tasks aren't independent, you may still be able to order them in a way to make them more efficient when executed.

Whether or not to use Task.Run, however is an important distinction. Task.Run will use a ThreadPool thread, which if it's doing something synchronous will block, and therefore reduce the number of ThreadPool theads available to your application. If you have many users, and you are executing many tasks, this could be a problem.

Try to find async api's rather than using Task.Run.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291