Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Not much difference between ASP.NET Core sync and async controller actions

I wrote a couple of action methods in a controller to test the difference between sync and async controller actions in ASP.NET core:

[Route("api/syncvasync")] public class SyncVAsyncController : Controller {     [HttpGet("sync")]     public IActionResult SyncGet()     {         Task.Delay(200).Wait();          return Ok(new { });     }      [HttpGet("async")]     public async Task<IActionResult> AsyncGet()     {         await Task.Delay(200);          return Ok(new { });     } } 

I then load tested the sync end point: enter image description here

... followed by the async end point: enter image description here

Here's the results if I increase the delay to 1000 ms enter image description here enter image description here

As you can see there is not much difference in the requests per second - I expected the async end point to handle more requests per second. Am I missing something?

like image 633
Carl Rippon Avatar asked Dec 28 '17 22:12

Carl Rippon


People also ask

Is ASP net synchronous or asynchronous?

NET Framework 4 introduced an asynchronous programming concept referred to as a Task and ASP.NET 4.5 supports Task. Tasks are represented by the Task type and related types in the System.

Should I use async controller actions?

async actions help best when the actions does some I\O operations to DB or some network bound calls where the thread that processes the request will be stalled before it gets answer from the DB or network bound call which you just invoked.

What is synchronous and asynchronous in .NET Core?

ASP.NET Core apps should be designed to process many requests simultaneously. Asynchronous APIs allow a small pool of threads to handle thousands of concurrent requests by not waiting on blocking calls. Rather than waiting on a long-running synchronous task to complete, the thread can work on another request.

What is the primary purpose of using an async controller action?

The asynchronous controller enables you to write asynchronous action methods. It allows you to perform long running operation(s) without making the running thread idle. It does not mean it will take lesser time to complete the action.


1 Answers

Yes, you are missing the fact that async is not about speed, and is only slightly related to the concept of requests per second.

Async does one thing and only one thing. If a task is being awaited, and that task does not involve CPU-bound work, and as a result, the thread becomes idle, then, that thread potentially could be released to return to the pool to do other work.

That's it. Async in a nutshell. The point of async is to utilize resources more efficiently. In situations where you might have had threads tied up, just sitting there tapping their toes, waiting for some I/O operation to complete, they can instead be tasked with other work. This results in two very important ideas you should internalize:

  1. Async != faster. In fact, async is slower. There's overhead involved in an asynchronous operation: context switching, data being shuffled on and off the heap, etc. That adds up to additional processing time. Even if we're only talking microseconds in some cases, async will always be slower than an equivalent sync process. Period. Full stop.

  2. Async only buys you anything if your server is at load. It's only at times when your server is stressed that async will give it some much needed breathing room, whereas sync might bring it to its knees. It's all about scale. If your server is only fielding a minuscule amount of requests, you very likely will never see a difference over sync, and like I said, you may end up using more resources, ironically, because of the overhead involved.

That doesn't mean you shouldn't use async. Even if your app isn't popular today, it doesn't mean it won't be later, and rejiggering all your code at that point to support async will be a nightmare. The performance cost of async is usually negligible, and if you do end up needing it, it'll be a life-saver.

UPDATE

In the regard of keeping the performance cost of async negligible, there's a few helpful tips, that aren't obvious or really spelled out that well in most discussions of async in C#.

  • Use ConfigureAwait(false) as much as you possibly can.

    await DoSomethingAsync().ConfigureAwait(false); 

    Pretty much every asynchronous method call should be followed by this except for a few specific exceptions. ConfigureAwait(false) tells the runtime that you don't need the synchronization context preserved during the async operation. By default when you await an async operation an object is created to preserve thread locals between thread switches. This takes up a large part of the processing time involved in handling an async operation, and in many cases is completely unnecessary. The only places it really matters is in things like action methods, UI threads, etc - places where there's information tied to the thread that needs to be preserved. You only need to preserve this context once, so as long as your action method, for example, awaits an async operation with the synchronization context intact, that operation itself can perform other async operations where the synchronization context is not preserved. Because of this, you should confine uses of await to a minimum in things like action methods, and instead try to group multiple async operations into a single async method that that action method can call. This will reduce the overhead involved in using async. It's worth noting that this is only a concern for actions in ASP.NET MVC. ASP.NET Core utilizes a dependency injection model instead of statics, so there are no thread locals to be concerned about. In others you can use ConfigureAwait(false) in an ASP.NET Core action, but not in ASP.NET MVC. In fact, if you try, you'll get a runtime error.

  • As much as possible, you should reduce the amount of locals that need to be preserved. Variables that you initialize before calling await are added to the heap and the popped back off once the task has completed. The more you've declared, the more that goes onto the heap. In particular large object graphs can wreck havoc here, because that's a ton of information to move on and off the heap. Sometimes this is unavoidable, but it's something to be mindful of.

  • When possible, elide the async/await keywords. Consider the following for example:

    public async Task DoSomethingAsync() {     await DoSomethingElseAsync(); } 

    Here, DoSomethingElseAsync returns a Task that is awaited and unwrapped. Then, a new Task is created to return from DoSometingAsync. However, if instead, you wrote the method as:

    public Task DoSomethingAsync() {     return DoSomethingElseAsync(); } 

    The Task returned by DoSomethingElseAsync is returned directly by DoSomethingAsync. This reduces a significant amount of overhead.

like image 196
Chris Pratt Avatar answered Sep 18 '22 22:09

Chris Pratt