C#中AsyncMethodBuilder.Start()方法为何需要检查线程上下文变更?
Great question—your observation that Start() runs synchronously is spot-on, but those context checks exist for a critical reason that's easy to miss: even though Start() itself doesn't switch threads, the MoveNext() call it makes can quietly modify the calling thread's context, and the runtime needs to clean up after that.
Let's break this down step by step, using the IL you shared as a reference:
1. MoveNext() can modify the thread's context (even without an await)
The state machine's MoveNext() method (generated by the C# compiler for your async method) isn't limited to just running your code and hitting awaits. It could:
- Explicitly call APIs like
SynchronizationContext.SetSynchronizationContext()orExecutionContext.SuppressFlow()as part of your user code. - Implicitly trigger context changes via library code, logging frameworks, or other components that run during the initial synchronous phase of your async method.
Even if your async method doesn't have an await at all (or the first await completes synchronously),MoveNext()might still alter the calling thread's context state.
2. The runtime must avoid "polluting" the calling thread
When you call Start() to kick off an async state machine, you expect the thread you're using to remain in its original state after Start() returns. If MoveNext() changed the thread's SynchronizationContext or ExecutionContext, leaving those changes in place would break code that runs later on the same thread.
Looking at the IL:
- The code first captures the original
_synchronizationContext(IL_0025-002b) and_executionContext(IL_001c-0022) of the calling thread. - In the
finallyblock (guaranteed to run even ifMoveNext()throws), it compares the captured original contexts to the thread's current contexts. If they don't match, it restores the original values:- For
SynchronizationContext: It overwrites the thread's current context with the original one (IL_004d-0051). - For
ExecutionContext: It callsRestoreChangedContextToThread()to safely revert any changes (IL_0068-006e).
- For
3. It's a defensive, future-proof design
The C# compiler generates MoveNext() based on your async code, but the runtime can't predict exactly what that code will do. These checks are a form of defensive programming:
- They ensure compatibility with any user code or library that might modify context during the synchronous phase of an async method.
- They account for potential future changes to how the compiler generates state machine code, ensuring the runtime maintains thread context consistency regardless.
In short: Start()'s job isn't just to run the state machine—it's to ensure that the calling thread is left in the exact state it was in before Start() was called, even if the state machine's code messed with the context along the way.
内容的提问来源于stack exchange,提问作者Y.L

