1

I'm implementing several small services, each of which uses entity-framework to store certain (but little) data. They also have a fair bit of business-logic so it makes sense to separate them from one another.

I'm certainly aware that async-methods and the async-await pattern itself can solve many problems in regards to performance especially when it comes to any I/O or cpu-intensive operations.

I'm uncertain wether to use the async-methods of entity-framework logic (e.g. SaveChangesAsync or FirstOrDefaultAsync) because I can't find metrics that say "now you do it, and now you don't" besides from "Is it I/O or CPU-Intensive or not?".

What I've found when researching this topic (not limited to this but these are showing the problem):

  • not using it can lead to your application stopping to respond because the threads (not the ones of the cpu, but virtual threads of the os) can run out because of the in that case blocking i/o calls to the database.

  • using it bloats your code and decreases performance because of the context-switches at every method. Especially when I apply those to entity-framework calls it means that I have at least three context switches for one call from controller to business-logic to the repository to the database.

What I don't know, and that's what I would like to know from you:

  • How many virtual os threads are there? Or to be more precise: If I expect my application and server to be able to handle 100 requests to this service within five seconds (and I don't expect them to be more, 100 is already exagerated), should I back away from using async/await there?

  • What are the precise metrics that I could look at to answer this question for any of my services?

  • Or should I rather always use async-methods for I/O calls because they are already there and it could always happen that the load-situation on my server changes and there's so much going on that the async-methods would help me a great deal with that?

Tom Rieck
  • 21
  • 2
  • "I can't find metrics that say "now you do it, and now you don't" besides from "Is it I/O or CPU-Intensive or not?"." Well - that is the one defining metric on whether it would be smart to move logic to Async methods or not... do you not understand this or what are you asking here? – sommmen Jun 10 '21 at 08:34
  • one thing to consider is that in some true asynchronous paths... there is no thread (see https://blog.stephencleary.com/2013/11/there-is-no-thread.html) so by using async await you free up threads to handle more requests. My opinion is there is unfortunately, no clear cut black and white answer and without more details really of your application no one will be able to give you a good break down. I would lean towards using async usually its pitfalls come in to lite in high performance/bottle neck code - normal day to day stuff, the benefits often outweigh the down sides – Dave Jun 10 '21 at 08:39
  • I would like to add that my research and the results of it that I stated here do not reflect my oppinions but are severily diluted by the "educated" states many here on stackoverflow did. So yes, what I wrote there might be wrong. I'm just asking actually because I got so confused while researching this. – Tom Rieck Jun 10 '21 at 12:19

2 Answers2

3

I'm certainly aware that async-methods and the async-await pattern itself can solve many problems in regards to performance especially when it comes to any I/O or cpu-intensive operations.

Sort of. The primary benefit of asynchronous code is that it frees up threads. UI apps (i.e., desktop/mobile) manifest this benefit in more responsive user interfaces. Services such as the ones you're writing manifest this benefit in better scalability - the performance benefits are only visible when under load. Also, services only receive this benefit from I/O operations; CPU-bound operations require a thread no matter what, so using await Task.Run on service applications doesn't help at all.

not using it can lead to your application stopping to respond because the threads (not the ones of the cpu, but virtual threads of the os) can run out because of the in that case blocking i/o calls to the database.

Yes. More specifically, the thread pool has a limited injection rate, so it can only grow so far so quickly. Asynchrony (freeing up threads) helps your service handle bursty traffic and heavy load. Quote:

Bear in mind that asynchronous code does not replace the thread pool. This isn’t thread pool or asynchronous code; it’s thread pool and asynchronous code. Asynchronous code allows your application to make optimum use of the thread pool. It takes the existing thread pool and turns it up to 11.

Next question:

using it bloats your code and decreases performance because of the context-switches at every method.

The main performance drawback to async is usually memory related. There's additional structures that need to be allocated to keep track of ongoing asynchronous work. In the synchronous world, the thread stack itself has this information.

What I don't know, and that's what I would like to know from you: [when should I use async?]

Generally speaking, you should use async for any new code doing I/O-based operations (including all EF operations). The metrics-based arguments are more about cost/benefit analysis of converting to async - i.e., given an existing old synchronous codebase, at what point is it worth investing the time to convert it to async.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
2

TLDR: Should I use async? YES!

You seem to have fallen for the most common mistake when trying to understand async/await. Async is orthogonal to multi-threading.

To answer your question, when should you the async method?

If currentContext.IsAsync && method.HasAsyncVersion
    return UseAsync.Yes;
Else
    return UseAsync.No;

That above is the short version.

Async/Await actually solves a few problems

  • Unblock UI thread
  • M:N threading
  • Multithreaded scheduling and synchronization
  • Interupt/Event based asynchronous scheduling

Given the large number of different use cases for async/await, the "assumptions" you state only apply to certain cases.

For example, context switching, only happens with Multi-Threading. Single-Threaded Interupt based Async actually reduces context switching by reducing blocking times and keeping the OS thread well fed with work.

Finally, your question on OS threads, is fundimentally wrong.

Firstly, OS threads each require creation of a stack (4MB of continous RAM, 100 threads means 400MB of RAM before any work is even done).

Secondly, unless you have 100 physical cores on your PC, your CPUs will have to context switch between each OS thread, resulting in the CPU stalling, whilst it loads that thread. By using M:N threading, you can keep the CPU running, by reducing the number of OS threads and instead using Green Threads (Task in dotnet).

Thirdly, not all "await" results in "async" behavior. Tasks are able to synchronously return, short-circuiting all of the "bloat".

In short, without digging really deep, it is hard to find optimization opportunities by switching from async to sync methods.

Aron
  • 15,464
  • 3
  • 31
  • 64
  • Upvoted. But I thought that OS threads require [1 MB](https://stackoverflow.com/questions/28656872/why-is-stack-size-in-c-sharp-exactly-1-mb), not 8. – Theodor Zoulias Jun 10 '21 at 11:59
  • 1
    @TheodorZoulias Looks like I misremembered. However, you should note that 1MB stack is ONLY for x86, x64 uses **4MB**, which is only a factor a 2 off... – Aron Jun 11 '21 at 01:50