Я відповім на ваші конкретні запитання нижче, але ви, швидше за все, непогано прочитати мої обширні статті про те, як ми створили прибутковість та очікування.
https://blogs.msdn.microsoft.com/ericlippert/tag/continuation-passing-style/
https://blogs.msdn.microsoft.com/ericlippert/tag/iterators/
https://blogs.msdn.microsoft.com/ericlippert/tag/async/
Деякі з цих статей зараз застаріли; генерований код відрізняється багатьма способами. Але це, безумовно, дасть вам уявлення про те, як це працює.
Крім того, якщо ви не розумієте, як створюються лямбда як класи закриття, зрозумійте це спочатку . Ви не будете робити голови чи хвости асинхронізувати, якщо у вас немає лямбдашів.
Коли чекання буде досягнуто, як час виконання буде знати, який фрагмент коду повинен виконувати далі?
await
генерується як:
if (the task is not completed)
assign a delegate which executes the remainder of the method as the continuation of the task
return to the caller
else
execute the remainder of the method now
Це в основному все. Очікування - це просто фантазійне повернення.
Звідки він знає, коли він може відновитись, де зупинився, і як він пам’ятає, де?
Ну, як це зробити, не чекаючи? Коли метод foo викликає рядок методу, ми якось пам'ятаємо, як повернутися до середини кадра, з усіма місцевими жителями активації foo неушкодженими, незалежно від того, що робить бар.
Ви знаєте, як це робиться в асемблері. Запис активації для foo висувається на стек; він містить значення місцевих жителів. У точці дзвінка зворотну адресу в foo висувають на стек. Коли бар виконаний, вказівник стека та вказівник інструкцій скидаються туди, де вони повинні бути, і foo продовжує рухатися з того місця, де він зупинився.
Продовження очікування точно таке саме, за винятком того, що запис ставиться на купу з очевидних причин того, що послідовність активацій не утворює стек .
Делегат, який очікує, надає як продовження завдання, містить (1) число, яке є входом до таблиці пошуку, яка дає вказівник інструкції, який потрібно виконати далі, та (2) всі значення локальних та тимчасових країн.
Там є якась додаткова передача; Наприклад, у .NET заборонено розгалужувати середину блоку спробу, тому ви не можете просто вставити адресу коду всередині блоку спробу в таблицю. Але це деталі бухгалтерії. Концептуально запис активації просто переміщується на купу.
Що відбувається з поточним стеком викликів, чи його якось зберігають?
Відповідна інформація в поточному записі активації ніколи не ставиться в стек в першу чергу; він виділяється поза грою з початку руху. (Ну, формальні параметри передаються в стек або в регістри нормально, а потім копіюються в кучу, коли метод починається.)
Записи активації абонентів не зберігаються; очікуючи, ймовірно, повернеться до них, пам’ятайте, тож вони будуть мати справу з нормально.
Зауважте, що це германна різниця між спрощеним стилем продовження проходження стилю очікування та справжніми структурами виклику з поточним продовженням, які ви бачите на таких мовах, як схема. У цих мовах все продовження, включаючи продовження назад у абонентів, фіксується call-cc .
Що робити, якщо метод виклику робить інші виклики методу, перш ніж він чекає-- чому стек не перезаписується?
Ці виклики методу повертаються, і тому записи їх активації більше не стоять на стеку в момент очікування.
І як, на землі, час виконання буде проходити все це у випадку винятку та стеку?
У випадку невдалого винятку виняток перехоплюється, зберігається всередині завдання та повторно викидається при отриманні результату завдання.
Пам'ятаєте все те бухгалтерію, про яке я згадував раніше? Правильне визначення семантики виключень було величезним болем, дозвольте сказати.
Коли дохідність буде досягнута, як час виконання слідкує за точкою, де потрібно підібрати речі? Як зберігається стан ітератора?
Точно так же. Стан місцевих жителів переміщується на купу, і номер, що представляє інструкцію, яка MoveNext
повинна відновитись наступного разу, коли вона буде викликана, зберігається разом з місцевими жителями.
І знову: в ітераторі є купа передач, щоб переконатися, що винятки керовані правильно.