3

I have a newbie question about controller methods in ASP.Net Core. I have the two different implementations below, one is synchronous and the other is asynchronous.

Question - I understand the difference between sync and async, I think, but when it comes to entering a controllers method, will ASP.Net block all users from entering into the sync method if another user is inside it and is still in the process of running through it?

And if so, if I anticipate thousands of concurrent users then this sync approach would drastically slow my app down right?

So I should use async controller calls pretty much everywhere? Unless I specifically want to lock another user out!

Here is async

    [HttpPost]
    public async Task<IActionResult> PostRecordAsync([FromBody] Record record)
    {
        record = await RecordsContext.PostRecordAsync(_context, record);
        return Ok(record);
    }

    public static async Task<Record> PostRecordAsync(MpidDbContext context, Record record)
    {
        context.Records.Add(record);
        await context.SaveChangesAsync();
        return record;
    }

and non async

    [HttpPost]
    public ActionResult PostRecord([FromBody] Record record)
    {
        record = RecordsContext.PostRecord(_context, record);
        return Ok(record);
    }

    public static Record PostRecord(MpidDbContext context, Record record)
    {
        context.Records.Add(record);
        context.SaveChanges();
        return record;
    }
chuckd
  • 13,460
  • 29
  • 152
  • 331
  • 1
    `will ASP.Net block all users from entering into the sync method` no this is not the case – TheGeneral May 14 '19 at 23:03
  • Async is a scalabilty feature which frees up thead-pool threads waiting for IO completion ports, its lighter on the thread resources so to speak, and hence allows more scalability, allow even more users, ect (terms used loosely) – TheGeneral May 14 '19 at 23:05
  • When building web applications the rule of thumb is, if it can be *async* you might as well take advantage of it, unless there is a compelling case not to (for which i am not sure i could even think of an example) – TheGeneral May 14 '19 at 23:08
  • 1
    ASP.NET creates a new controller object for each request so each user/caller will get there own class and method to execute and not a shared one (unless you declare a method as `static` in which case they do share the same method). – Simply Ged May 14 '19 at 23:42

1 Answers1

4

No, synchronous calls will not block. For web applications the async methods will free up web server threads to respond to more requests while previous calls are waiting on responses from resource or compute intensive operations.

As a simple example. Lets say I have a web server that is running 100 threads. This means it can handle 100 concurrent requests. Each request takes up to 5 seconds to complete due to handing off to a database to do some reads and updates.

With synchronous calls, if I have 500 active users hitting my web server, the first 100 will take up connection threads while the rest wait their turn for a thread. As operations complete, the next waiting requests will get a thread. Requests can potentially timeout as they wait for a thread to process them.

With asynchronous calls, if I have 500 active users hitting my web server, the first 100 will take up a connection, but as the operations are awaited and running on the database, the web server thread is made available again, starting off handling another request. As operations are completed, an available web server thread can be requested to handle the result and ultimately pass back the response. The web server effectively acts in a more responsive manner for handling more concurrent requests by accepting requests and kicking them off without waiting for the previous ones to complete.

Async does incur a small performance cost in that it needs to be able to resume execution on another thread, or wait on a synchronization context. For operations that can take seconds to run, this is more than worth it, but for methods that can be expected to always complete quickly this is adding a small, but unnecessary performance cost to all calls. As a general rule I default to synchronous calls, then introduce async where I know there will be more than a half second or so processing time. (Significant Database calls like ToList or complex queries, file I/O, etc.)

When working with ASP.Net Core you also need to be cautious with async code in that it operates without a synchronization context. Code that uses async while referencing non-thread-safe code like a DbContext can have issues where multiple async calls occur without awaiting each in turn.

I.e.

// Should be Ok...
var product = await context.Products.SingleAsync(x => x.ProductId == productId);
var customer = await context.Customers.SingleAsync(x => x.CustomerId == customerId);
var newOrder = new Order { Product = product, Customer = customer};

// Not Ok...
var productGet = context.Products.SingleAsync(x => x.ProductId == productId);
var customerGet = context.Customers.SingleAsync(x => x.CustomerId == customerId);
var newOrder = new Order { Product = await productGet, Customer = await customerGet};

Not the best example, but async operations will execute on a worker thread so the second example would have the two context operations occurring together on different threads. The first example will run each operation on separate threads, but the 2nd operation will only run once the first has completed. The second will run the operations concurrently. It's probably not going to manifest a problem unless you are running async operations against the same tables or related tables. I can't be specific on the implications of code like this but it is something to watch out for if you do experience problems working within async methods.

Steve Py
  • 26,149
  • 3
  • 25
  • 43
  • "but async operations will execute on a worker thread" is not exactly true... one common reason to use `async` is simplify coming back to original thread to continue execution - unless code specifically designed to run on worker thread it will schedule continuation sequentially on "original" context (which may or may not be the same thread)… unless some special effort taken the sample you've shown will not run code in parallel for same request (DB calls will start in parallel) – Alexei Levenkov May 15 '19 at 00:49
  • 2
    The thread behavior will depend on the presence and implementation of a synchronization context. I can't speak for EF's DBContext implementation but if you create 2 async methods called by a third method and capture the current thread ID in each method you can see that the thread IDs of the internal async code differ from one another. Awaiting one before calling the other will still be different thread IDs, but not run in parallel. Awaiting both at the end will have both running parallel. While DB queries may safely run cross-thread, checks & actions against local cache likely aren't. – Steve Py May 15 '19 at 01:38
  • Steve - you are right... and my comment does not apply to this question... Somehow I missed "Core" in "ASP.Net Core" which indeed no longer synchronizes calls (unlike ASP.Net) - https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html – Alexei Levenkov May 15 '19 at 01:45