4

Exploring the async/await State Machine – Series Overview

 3 years ago
source link: https://vkontech.com/exploring-the-async-await-state-machine-series-overview/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

What’s It All About?

The C# compiler does an enormous amount of work so that we can write asynchronous code that looks almost identical to the synchronous version.

We pretty much just put the async and await keywords here and there, ending up with an asynchronous execution with all the benefits coming with that.

Gone are the days when we were manually passing callbacks as continuations leading to all sorts of complexities and maintainability issues.

This async/await convenience is almost magical as asynchronous programming is a lot different in its very nature. It consists of all sorts of challenges like attaching continuations, preserving state, flowing all kinds of contexts, propagating errors, etc.

I find the simplicity we end up with pretty fascinating. That’s why I decided to go for a deep dive and explore the topic in detail, accompanying the research with several articles.

The Articles

Let me give a brief summary of each article and describe how it contributes to the full picture.

Part #1 – The Awaitable Pattern

The Awaitable Pattern plays a vital role in the whole async/await workflow behind the scenes. The best way to understand how it works is to build your own awaitable type. This is what you will do in this post.

To reveal some of the content, we’ll go through every step of evaluating an await expression:

Figure 1. Evaluation of an await expression

Key Concepts Covered In Part #1

  • The core methods of the TaskAwaiter and their purpose
  • The specific requirements to create your own awaitable type
  • How do we attach continuations
  • How is an await expression evaluated concretely
  • Building your own awaitable type

Part #2 – Main Workflow and State Transitions

After getting familiar with the Awaitable Pattern, we take a high-level overview of the State Machine itself.

This article contains almost no code. However, it lays the crucial foundations of the whole async/await workflow.

Concretely, you will explore all the components of the State Machine and their interactions.

The source of truth for the discussion will be the following diagram:

Figure 2: The State Machine – Overview

Key Concepts Covered In Part #2

  • How is the State Machine triggered
  • How the await statements mark the different states of the State Machine
  • The MoveNext method as the main driver of the State Machine
  • How does the State Machine move through states, and how is the data preserved
  • How and when are all the elements of the Awaitable Pattern being used

If you stop your exploration after reading this article – you will still have a pretty strong fundamental understanding of the async/await process.

Of course, I will advise you to take at least one step further and read the next post too.

With it, you will see some real and functional implementation of the async/await State Machine!

Part #3 – Conceptual Implementation

After getting enough high-level understanding of the async/await State Machine from Part #2, it’s time to get your hands dirty and see some actual implementation!

This implementation will be a little bit simplified. The reason is that the actual code is full of trickeries to achieve optimal performance and memory footprint.

Although these optimizations are quite substantial (and exciting to review), they don’t contribute to the general understanding of the State Machine. That’s why I decided to provide such a “conceptual implementation.”

Although I’ll simplify a thing or two, the implementation you’ll see in this post will be pretty close to the real one. What’s more – it will be fully functional!

Key Concepts Covered In Part #3

  • How the initial async method is transformed so that it triggers the State Machine and returns the task
  • The MoveNext implementation
  • How is the state of the State Machine preserved
  • How are the initial async method input parameters handled
  • The exact mechanics of attaching continuations and moving between states
  • What is the lifecycle of the resulting task

Getting through this article will leave you with an in-depth understanding of the async/await State Machine.

If you are not too interested in the advanced techniques in the actual implementation, you may stop here.

However, I’d advise you to at least walk through the next part. Quite possible you may discover something you’ve always been wondering about!

Part #4 – Concrete Implementation

This is the part when we get into the nitty-gritty details of the actual State Machine, produced by the compiler.

This is the kind of knowledge that you don’t need on a daily basis but truly get your skills to the next level.

The details you’ll see here will make you appreciate all the efforts and reasoning behind a real-world implementation used by millions of users.

I find such in-depth explorations as quite valuable. The takeaways are far more overreaching than just understanding some limited piece of functionality. It’s not only a lot of fun, but it takes you further on the path of becoming an expert in your programming ecosystem.

Key Concepts Covered In Part #4

  • How and why the State Machine is kept on the execution stack if all the awaiters are already completed
  • How is the State Machine boxed onto the heap exactly once in order to preserve its state across continuations
  • What is the ExecutionContext, and how does it flow
  • What are the unsafe methods in the async infrastructure, and how they help with performance

Part #5 – Synchronization Context

In this part, I’ll start exploring some of the most popular questions related to async/await.

Concretely, in the next couple of posts, I’ll clarify the misconceptions around the Synchronization Context and the usage of ConfigureAwait in nested and sequential async calls.

The Synchronization Context is an abstraction that lets you run a piece of code asynchronously without thinking about the specifics of the current environment. How does it work, and when is it invoked as part of the async workflow?

Key Concepts Covered In Part #5

  • The concept of Synchronization Context and its’ implementations in different context-specific frameworks.
  • How you can create and assign your own Synchronization Context.
  • When and how the Synchronization Context is captured and propagated in the async infrastructure codebase.

Armed with the knowledge so far, I’ll move on with the series by answering some of the most common async/await questions related to nested async calls and the usage of ConfigureAwait.

Summary

Going through the full series is an activity that would take you some time, and it requires concentration and focus.

However, I think the effort is entirely worth it.

This foundational knowledge will help you understand and answer most of the practical questions you may have had about async methods in C#.

Thanks for reading!

Resources


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK