2

My goal is increase scalability of a Web API I am working on. To that end, I've implemented the controller actions to be async. The idea being that request threads are released and are available to handle other incoming requests. However, the winning answer of Effectively use async/await with ASP.NET Web API states:

You'd need a truly asynchronous implementation to get the scalability benefits of async.

Eventually my code needs to call a method from a 3rd party library that is very synchronous, single threaded and I/O bound. So really the only way to do it is through Task.Run(), which I am assuming will hold on to the thread - thereby cancelling the benefits of async/await.

So is there a way to realize the benefits of async/await in a Web API scenario when there is a synchronous operation in the mix?

AngryHacker
  • 59,598
  • 102
  • 325
  • 594
  • 2
    If you have a synchronous 3rd party IO call to make, just let it happen and make the current thread block, no use offloading it to a threadpool thread just to also block, less overhead is involved in the first case. If you need high throughput, publishing a message to some queue and let it a subscriber take care of it at some point will help but that is only if your http call can return to the client with the 3rd party call being made later on. – JohanP Oct 07 '19 at 23:10
  • 1
    If these Api's use *Asynchronous Programming Model (APM)* ie begin and end type methods , or *Event-based asynchronous pattern (EAP)*, you can wrap these in a way that will not tie up a threadpool threads You can also offload thread blocking calls to non threadpool threads, which is not the same as waiting for a postback from a *completion port* however may be more scalable compared to `Task.Run`. It really depends on what you want to achieve here – TheGeneral Oct 07 '19 at 23:11
  • @TheGeneral How does one offload thread blocking calls to non thread-pool threads? – AngryHacker Oct 07 '19 at 23:14
  • 1
    Asynchronous methods call synchronous methods all the time. This is not a problem (usually). It’s when you have a synchronous method calling an asynchronous method that is a problem. – John Wu Oct 08 '19 at 00:24

2 Answers2

5

So is there a way to realize the benefits of async/await in a Web API scenario when there is a synchronous operation in the mix?

No. The reason is simple: asynchrony provides its benefits by releasing its thread, and a synchronous operation blocks a thread.

The benefit of asynchrony on the server side is scalability. Asynchrony provides better scalability because it releases its thread when it's not needed.

A synchronous API blocks a thread, by definition. If the API is an I/O-bound operation, then it's blocking a thread that is mainly just waiting for I/O, i.e., the thread isn't needed but it also can't be released.

There's no way around this; the synchronous API must block a thread, so you cannot get the benefits of asynchrony.

If you had a GUI application, then there is a workaround. On the client side the primary benefit of asynchrony is responsiveness. Asynchrony provides better responsiveness by releasing the UI thread when it's not needed.

For GUI applications, you can use Task.Run to block a thread pool thread, and your UI thread remains responsive; this is an appropriate workaround. On server applications, using Task.Run like that is an antipattern, since it forces a thread switch and then you still end up with a blocked thread anyway (preventing any scalability benefit), so Task.Run just slows down your code for no benefit at all.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Follow up question. Your article is very informative and illuminating. It mostly references asp.net 4.5. In there anything in the article that is categorically not true when applied to .Net Core 2.x or higher? – AngryHacker Oct 10 '19 at 06:25
  • 1
    @AngryHacker: The conceptual parts all carry over to ASP.NET Core. The second half which deals more with specific APIs no longer applies. – Stephen Cleary Oct 10 '19 at 12:22
1

It all depends on what other code is involved. If there is a lot of other code that could be running while the I/O operations are executing then yes the Task.Run() would be ideal, just make sure to await the task where needed.

If there isn't code that could be running during the I/O operations then the Task.Run() is not a good plan as you would be blocking the main thread and adding a new thread anyways yielding worse scaling than just leaving it synchronous.

Ultimately if you are using a 3rd party synchronous library and want to optimize scaling your web API via async calls you should try to recreate the library yourself using async I/O calls. Feel free to check out https://learn.microsoft.com/en-us/dotnet/standard/io/asynchronous-file-i-o for more information.

CVarg
  • 69
  • 10