Home > database >  When you await on async call--is it really asynchronous programming in C#
When you await on async call--is it really asynchronous programming in C#

Time:01-06

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;
}

CodePudding user response:

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.

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

CodePudding user response:

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.

CodePudding user response:

Its very simple, don't think about just the keywords. 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( (re) => { // 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. 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. So although it looks sequential to developer it is still asynchronous.

CodePudding user response:

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.

  • Related