Почему продолжение начинается перед GetResult()?

#c# #async-await

#c# #асинхронное ожидание

Вопрос:

 class MyAwaitable
{
    public MyAwaiter GetAwaiter()
    {
        return new MyAwaiter();
    }
    public class MyAwaiter : INotifyCompletion
    {
        public bool IsCompleted { get; }
        public void GetResult()
        {
            Console.WriteLine("GetResult().");
        }
        public void OnCompleted(Action continuation)
        {
            Console.WriteLine("Continuation begins.");
            continuation();
            Console.WriteLine("Continuation ends.");
        }
    }
}



class Program
{
    static async Task Main()
    {
        await new MyAwaitable();
        Console.WriteLine("The last code.");
    }
}
  

Вывод:

Начинается продолжение.

GetResult().

Последний код.

Продолжение заканчивается.

Вопрос

Я действительно не понимаю, почему Continuation begins. это было раньше GetResult(). . По моей интуиции, GetResult() должно быть первым.

Почему продолжение начинается перед GetResult() ?

Комментарии:

1. Просто в качестве примечания, вызов continuation внутри OnCompleted может потенциально привести к переполнению стека, даже если пользовательский код выглядит довольно итеративным (см. Эту скрипку ).

Ответ №1:

continuation это делегат, который выполняется, когда ожидаемое завершается. Обычно TaskAwaiter ожидаемое сохранит continuation в поле и запустит его, когда Task завершится.

Сгенерированный компилятором конечный автомат использует это для перехода к следующему состоянию, которое, в свою очередь, вызовет .GetResult() ожидаемую вещь (как для получения результата, так и для создания любых исключений);

Итак, continuation это делегат, который в конечном итоге вызывает GetResult() .

Вы можете найти, где OnCompleted вызывается здесь. continuation является результатом того, AsyncMethodBuilderCore.GetCompletionAction из MoveNextRunner.Run которого оно создается, который вызывает MoveNext конечный автомат.

Если вы посмотрите на сгенерированный компилятором код для вашего асинхронного метода, вы увидите, что он MoveNext вызывает awaiter.GetResult() .

Чтобы увидеть, как Task работает здесь, начните с TaskAwaiter.OnCompleted и посмотрите, что он вызывает Task.SetContinuationForAwait , который вызывает AddTaskContinuation , который в конечном итоге сохраняет его в m_continuationObject . Затем это вызывается в Task.FinishContinuations .