the old asp.net created a thread per request, but this was not the most performant. Asp.net core only creates a thread per core. While this is less threads, performing async operations free the thread to do other work. This give asp.net core more than an order (10x) performance increase.
In practice this means actions (or razor pages models) should always use the async version of a library call, or if heavy compute bound, create a thread for the computation. all network, file and database calls are async by nature. you should never use the sync version or the sync helper .Result.
note: node.js which is single threaded, but uses an async pipeline was much more performant than asp.net. Core followed this model (even using the node.js pipeline code - which has been replaced) but creates a separate pipeline thread per cpu.
the reason for this design is because context switches between threads, while cheaper than a process switch, are still not free.
additional note: there is overhead in the async processing, so if the action has no async calls or tasks, it should not be declared async.