3

I have three tier .net web api application. each tier is implementing a async method. Each call is async and and we are using await at each step. My question is when you await at every step, doesn't it make the application synchronous & defeats the purpose ? What value async await really provides? Does it automatically uses multiple CPU cores?

Controller:

public async Task<IHttpActionResult> Get()
{
    return Ok(await _service.Create());
}

middle tier:

public async Task<MyObject> Create()
{
    return await _repository.CreateAsync(new MyDataObject());
}

DB

public async Task<T> CreateAsync(T data)
{
    var o = _dbSet.Add(data);
    await _dbContext.SaveChangesAsync();
    return 100;
}
11
  • async != multithreading (in case that's the thinking that led to your questions). Commented Jan 6, 2023 at 2:53
  • @ProgrammingLlama : Thank you for the comment but I am not thinking of multithreading here . I am just thinking about benefits of async await Commented Jan 6, 2023 at 2:55
  • If you're not thinking of multithreading, why do you think that it might automatically use multiple CPU cores? Commented Jan 6, 2023 at 2:56
  • @ProgrammingLlama: Since I could not see any value addition by async methods here. that made me think does it runs on new thread ? Commented Jan 6, 2023 at 3:01
  • 3
    I kindly remind to all participants that the comments are intended for asking for clarifications about the question, or suggesting improvements for the question, not for answering the question. Commented Jan 6, 2023 at 3:43

5 Answers 5

5

In your example you're definitely doing asynchronous processing. What you're not doing is parallel processing. The .NET documentation doesn't always do a good job of differentiating between the two. I'll just cover asynchronous here.

The main idea behind async / await (which your examples do well) is to not have a thread sitting around doing nothing when it could be doing something useful.

Take your CreateAsync example:

public async Task<T> CreateAsync(T data)
{
    var o = _dbSet.Add(data);
    await _dbContext.SaveChangesAsync();
    return 100;
}

The database changes won't happen any faster because of the await. So what's the benefit? For the sake of argument assume the following:

  • Your app has 5 users
  • Your app has 2 threads available (way low, but play along)
  • Each database save takes 3 seconds

Now assume User A and User B save a record at the same time. Each has their own thread (that's a fact, not an assumption). If you aren't using await then users A and B will hold both of your available threads for 3 seconds just waiting for the save to complete. No more threads are available, so users C, D and E will be blocked for that period.

When you use await, the threads are released because they're just... waiting. Now they can take care of users C, D, and E. When the save completes the threads will come back for users A and B and finish the CreateAsync method.

Bottom line, when async / await is done properly n threads can fulfill way more than n requests.

This is a simplified explanation, but I really wish I'd gotten a simple explanation way back when. I hope this helps.

Sign up to request clarification or add additional context in comments.

5 Comments

Makes sense. This makes me wonder, during waiting period when thread is freed, who is completing the task of dealing with DB ?
The db write is sent; the db is doing the write; there's no sense in blocking an API thread just waiting for the db to respond. Also see intro to async on ASP.NET and there is no thread, both by yours truly.
Great question! I left out a lot of detail on purpose because that's what the await / async implementation does too: it shields us from the gory details. Time for the next level: when I said the app had two threads I meant two worker threads, which do the actual work. There's also a main thread that manages all the workers. That's the one that notes when the waiting DB operation has a reply and fires up another worker thread (as soon as it's available) to finish the method.
@StephenCleary I wish I'd seen your articles years ago. I blush to think how long it took for async to click with me. I read reams of articles but they always managed to pollute the explanations with discursions into parallel processing or dive straight into details better left until later, leaving me thoroughly confused. Thanks so much for those links.
You're right @TheodorZoulias. Answer edited. I wanted to say more about how it reduces delays if there are > n+1 users, but a good artist should know when to stop :). Thanks for the feedback.
2

What value async await really provides?

It allows your web server to process more requests at the same time; async/await allows the threads to do other work while the code waits for the db to do its work (db or cache, email service, external api call, etc.).

From Async Programming : Introduction to Async/Await on ASP.NET:

(...) let’s say the requests in the system depend on some external resource, like a database or Web API. When a request comes in, ASP.NET takes one of its thread pool threads and assigns it to that request. Because it’s written synchronously, the request handler will call that external resource synchronously. This blocks the request thread until the call to the external resource returns.

...

Asynchronous request handlers operate differently. When a request comes in, ASP.NET takes one of its thread pool threads and assigns it to that request. This time the request handler will call that external resource asynchronously. This returns the request thread to the thread pool until the call to the external resource returns.

Comments

1

When you await an async method in C#, it does not necessarily mean that the method is executed asynchronously. It simply means that the calling method will be suspended until the awaited task completes.

The async and await keywords in C# are used to simplify the process of writing asynchronous code. They allow you to write code that looks and reads like synchronous code, while still being asynchronous under the hood.

To understand how async and await work, it is important to know that an async method actually returns a Task or Task object, which represents the asynchronous operation. The await operator is used to suspend the execution of the calling method until the awaited Task completes.

However, just because a method is marked async does not necessarily mean that it will be executed asynchronously. The actual execution of the method depends on how the Task is implemented and whether it is running on a thread pool thread or not.

For example, if the Task is running on a ThreadPool thread, it will be executed asynchronously. On the other hand, if the Task is implemented using the Task.Run method, it will be executed synchronously on a separate ThreadPool thread.

In short, the async and await keywords in C# allow you to write asynchronous code that is easier to read and maintain, but they do not guarantee that the code will be executed asynchronously.

2 Comments

"if the Task is running on a ThreadPool thread, it will be executed asynchronously. On the other hand, if the Task is implemented using the Task.Run method, it will be executed synchronously on a separate ThreadPool thread." -- I don't get the difference. In both cases a ThreadPool thread will do the work, so why one case is asynchronous and the other is not?
It doesn't make sense because it's a chatgpt answer.
1

Each call is async and and we are using await at each step. My question is when you await at every step, doesn't it make the application synchronous & defeats the purpose?

First of all, synchronous and asynchronous is a property of a method or an execution flow, not a property of the application as a whole. In your case you await on every level and every step all asynchronous operations (Tasks) that you start, so the execution flow of each web request is asynchronous. It's still a single execution flow though. The code is executed in a sequential/serialized fashion. In the context of a single web request, no concurrency is happening.

The difference from a classic and familiar synchronous execution flow is that the flow is not forced to run on the same thread from start to finish. Different threads might be involved in the execution, but not more than one thread at the same time. A thread-switch might occur at each await point, and whether it will happen depends on various factors that might not be under your direct control. The most interesting aspect of an asynchronous flow is that, time-wise, large parts of the execution are usually not running on any thread. For example while the _dbContext.SaveChangesAsync() is doing its job, there is no thread in your process that is blocked waiting for this work to be done. This means that your web application can have a large number of concurrent executions flows (web requests), using only a small number of threads. Which eventually results in increased scalability for your application: you can serve more users with less hardware.

Comments

1

Relatively new developers who come to asynchronous programming directly using async/await may have this confusion. To understand and appreciate the value of what async/await brings to table, one has to go slightly back in time and review how asynchronous code was written then.

Before the keywords were introduced in C#, asynchronous programming was still possible. Traditionally (.NET Framework 2.0 later), there were two patterns:

Event-based Asynchronous Pattern (EAP), where you subscribed to an event:

o.ReadCompleted += (o, e) => { // asynchronously called after ReadAsync completion }; 
o.ReadAsync(buffer, count); // begin the async process

Task-based Asynchronous Pattern (TAP), which is Promise like interface where you can setup completions with then and catch.

o.ReadAsync(buffer, count)
  .then( (result) => { // asynchronously called after ReadAsync completion } );

So, advantage of asynchronous programming on the server is that the calling thread is not blocked on the Read, which is now an asynchronous call as against a synchronous call. The thread can do some other tasks / or serve other requests.

When I/O bound Read eventually completes, the completion delegate will be called, possibly over a different thread. This way there are more threads available which makes the service more scalable. This is advantage of asynchronous programming.

However, the two patterns above have a disadvantage for the developer that the code becomes unreadable and inside-out because it is not written sequentially.

The async/await keywords just let us keep the same advantages of asynchronous programming, while making the code readable.

Now we just write:

var result = await o.ReadAsync(buffer, count);
// here comes code that we had to previously write inside the completion delegate
// but now its more readable, and much natural to handle exceptions

So although it looks sequential to developer it is still asynchronous. The compiler converts the async method into a state machine which behaves similar to code previously written, but hides away all the complex details from the developer. Similar to the completion delegate behavior, the code written after await could also be resumed on a different thread.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.