The compiler creates a state machine in the background when async and await are used.
I'll try to illustrate some of the high-level details by using the following example:
public async Task MyMethodAsync()
Task<int> longRunningTask = LongRunningOperationAsync();
// independent work which doesn't need the result of LongRunningOperationAsync can be done here
//and now we call await on the task
int result = await longRunningTask;
//use the result
public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation
await Task.Delay(1000); // 1 second delay
So this is what happens in the code above:
1. Task<int> longRunningTask = LongRunningOperationAsync(); starts running LongRunningOperation
2. Non- dependent work is finished on let's say some main thread (Thread ID = 1) then await LongRunningTask is reached.
MyMethodAsync() will now return to the calling method if the longRunningTask hasn't finished yet, preventing the main thread from becoming blocked. Any thread from the ThreadPool will return to MyMethodAsync() in its previous context after the longRunningTask has completed and continue to run (in this case printing the result to the console).