Await, Async, Task in depth

Await, Async, Task in depth

In the previous post, we explored how asynchronous methods free up threads in the thread pool and improve WebApp performance, but it still has opened questions:

1. How does the thread pool know when an async operation from external resources has finished its work?

2. How does the thread pool know where to continue execution after an async operation reaches the await point?

To answer this questions we will dive deeper into the Task-based Asynchronous Pattern (TAP), async/await, Task and state machine.


1. How does the thread pool know when an async operation from external resources has finished its work?

TAP and Task object

When we call ToListAsync() (or another async operation), it returns a Task object. This object represents the ongoing asynchronous operation. The thread pool doesn't need to continuously monitor this operation. Instead, the Task object manages the state of the async operation (whether it's running, completed, or faulted). The I/O operation happens outside the thread pool, managed by the OS or database. Once the database returns the result:

- The Task object marked as complete;

- The thread pool gets notified that the asynchronous operation has finished through the internal event system built into the .NET runtime. This doesn't require constant polling, it's more like a notification system where the task signals its completion

- The thread pool then schedules the continuation (the code that follows await query.ToListAsync()), assigning a thread to complete the remaining execution

So, the thread pool doesn’t directly monitor async operations. Instead, it relies on the Task object to track the state and completion of these operations.


2. How does the thread pool know where to continue execution after an async operation reaches the await point?

Async/Await and State Machine

Writing an async method with await (e.g., await query.ToListAsync()) causes the C# compiler to transform the method into a state machine. The next small async method

public async Task<string> ReadFileAsync(string path)
{
    Console.WriteLine("Start reading the file...");
    var fileContent = await File.ReadAllTextAsync(path);
    Console.WriteLine("The file has been successfully read.");
    return fileContent;
}        

will be transformed by compiler to the state machine:

This state machine keeps track of:

- The current state of the async method.

- The point of suspension (where the await occurs).

- The continuation point (where the method should resume after the awaited operation completes).

When the async method reaches the await keyword, the following happens:

- The method saves its current state (all local variables, the current execution point, etc.) and exits the method

- A Task is returned to the caller, representing the ongoing operation

At this point, the method is paused, and control is given back to the caller. The continuation (what comes after the await in the code) is saved as part of the state machine and won’t run until the async operation completes.

When the Task completes, it triggers a continuation:

- The continuation is the piece of code that comes after the await point, and the state machine knows exactly where to pick up because it has stored the state of the method.

- The thread pool schedules a thread to execute the continuation. It doesn’t have to be the same thread that initially started the operation; any available thread from the pool can resume the operation.

So, the result of the Task will be returned by any available thread from the pool, allowing the application to handle more requests concurrently.

Summary

  1. When awaitkeyword is used, the compiler creates a state machine that saves where the code should resume after the asynchronous operation finishes. The C# compiler generates a state machine for every async method, which stores the method’s state (including where the await happened).
  2. The Task object tracks the status of the async operation.
  3. When the Task completes, the thread pool assigns a thread to continue execution at the point that was saved before the await.
  4. This approach allows to freed the thread pool thread while waiting for the async I/O-bound operation to respond. The thread will be returned to the pool to handle other tasks.

要查看或添加评论,请登录

Max Bazhenov的更多文章

  • Concurrency control in Entity Framework Core

    Concurrency control in Entity Framework Core

    Concurrency control ensures data integrity when multiple users or processes interact with the database simultaneously…

  • Use try/catch with caution

    Use try/catch with caution

    The try/catch mechanism is used for handling exceptions, allowing the program to catch errors and handle them without…

  • Thread synchronization tools in details

    Thread synchronization tools in details

    Thread management in .NET offers multiple synchronization tools, each suited to different concurrency needs.

  • Parallelism management

    Parallelism management

    Parallelism in programming allows tasks to run concurrently, making full use of multi-core CPU architectures. By…

  • What is parallelism and how does it work

    What is parallelism and how does it work

    Previous posts covered asynchronous execution where we pass tasks to external resources and use the waiting time to do…

  • Best practices and antipatterns for Async/Await

    Best practices and antipatterns for Async/Await

    In the previous posts, we explored how async/await works, when to use them, and what benefits they provide. In this…

  • WebApp and system resources

    WebApp and system resources

    In the previous post, we introduced async operations and how they can optimize WebApp performance. But how exactly do…

    1 条评论
  • ToList() vs ToListAsync(). How does it work and where is benefit

    ToList() vs ToListAsync(). How does it work and where is benefit

    In this series of posts, we will look in depth at how async works and how the load is distributed between the WebApp…

  • Bundling and minificaiton

    Bundling and minificaiton

    Bundles and minificaiton in ASP.NET 4.

  • Angular standalone components

    Angular standalone components

    A standalone component is a type of component that doesn’t belong to any specific Angular module. Standalone component…