Saya akan menjawab pertanyaan spesifik Anda di bawah ini, tetapi sebaiknya Anda membaca artikel ekstensif saya tentang bagaimana kami merancang hasil dan menunggu.
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/
Beberapa dari artikel ini sudah usang sekarang; kode yang dihasilkan berbeda dalam banyak hal. Tapi ini pasti akan memberi Anda gambaran tentang cara kerjanya.
Juga, jika Anda tidak memahami bagaimana lambda dibuat sebagai kelas penutup, pahami itu terlebih dahulu . Anda tidak akan membuat kepala atau ekor menjadi asinkron jika Anda tidak memiliki lambda ke bawah.
Saat menunggu tercapai, bagaimana runtime mengetahui bagian kode apa yang harus dieksekusi selanjutnya?
await
dihasilkan sebagai:
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
Itu pada dasarnya. Menunggu hanyalah pengembalian yang mewah.
Bagaimana cara mengetahui kapan dapat dilanjutkan dari tempat yang ditinggalkannya, dan bagaimana ia mengingat di mana?
Nah, bagaimana Anda melakukannya tanpa menunggu? Ketika metode foo memanggil bilah metode, entah bagaimana kami ingat bagaimana kembali ke tengah-tengah foo, dengan semua penduduk lokal aktivasi foo utuh, tidak peduli bilah apa yang dilakukannya.
Anda tahu bagaimana hal itu dilakukan di assembler. Catatan aktivasi untuk foo didorong ke stack; itu mengandung nilai-nilai penduduk setempat. Pada titik panggilan, alamat pengirim di foo didorong ke stack. Ketika bilah selesai, penunjuk tumpukan dan penunjuk instruksi disetel ulang ke tempat yang mereka butuhkan dan foo terus berjalan dari tempat terakhirnya.
Kelanjutan dari menunggu persis sama, kecuali bahwa record diletakkan di heap karena alasan yang jelas bahwa urutan aktivasi tidak membentuk stack .
Delegasi yang menunggu diberikan sebagai kelanjutan dari tugas berisi (1) angka yang merupakan masukan ke tabel pencarian yang memberikan penunjuk instruksi yang perlu Anda jalankan selanjutnya, dan (2) semua nilai lokal dan temporari.
Ada beberapa perlengkapan tambahan di sana; misalnya, di .NET adalah ilegal untuk bercabang di tengah blok percobaan, jadi Anda tidak bisa begitu saja memasukkan alamat kode di dalam blok percobaan ke dalam tabel. Tapi ini adalah detail pembukuan. Secara konseptual, catatan aktivasi hanya dipindahkan ke heap.
Apa yang terjadi pada tumpukan panggilan saat ini, apakah itu disimpan entah bagaimana?
Informasi yang relevan dalam catatan aktivasi saat ini tidak pernah diletakkan di tumpukan sejak awal; itu dialokasikan dari heap sejak awal. (Nah, parameter formal diteruskan di stack atau di register secara normal dan kemudian disalin ke lokasi heap saat metode dimulai.)
Catatan aktivasi penelepon tidak disimpan; penantian mungkin akan kembali kepada mereka, ingat, jadi mereka akan ditangani secara normal.
Perhatikan bahwa ini adalah perbedaan erat antara gaya penerusan lanjutan yang disederhanakan dari menunggu, dan struktur panggilan-dengan-kelanjutan yang sebenarnya yang Anda lihat dalam bahasa seperti Skema. Dalam bahasa-bahasa tersebut seluruh kelanjutan termasuk kelanjutan kembali ke pemanggil ditangkap oleh panggilan-cc .
Bagaimana jika metode panggilan membuat panggilan metode lain sebelum menunggu-- mengapa tumpukan tidak ditimpa?
Panggilan metode tersebut kembali, sehingga catatan aktivasi mereka tidak lagi berada di tumpukan pada titik menunggu.
Dan bagaimana mungkin runtime bekerja melalui semua ini dalam kasus pengecualian dan tumpukan terlepas?
Jika terjadi pengecualian yang tidak tertangkap, pengecualian tersebut ditangkap, disimpan di dalam tugas, dan ditampilkan kembali saat hasil tugas diambil.
Ingat semua pembukuan yang saya sebutkan sebelumnya? Mendapatkan pengecualian semantik dengan benar adalah rasa sakit yang luar biasa, izinkan saya memberi tahu Anda.
Ketika hasil tercapai, bagaimana runtime melacak titik di mana hal-hal harus diambil? Bagaimana status iterator dipertahankan?
Cara yang sama. Status penduduk setempat dipindahkan ke heap, dan nomor yang mewakili instruksi yang MoveNext
harus dilanjutkan saat dipanggil berikutnya disimpan bersama dengan penduduk setempat.
Dan lagi, ada banyak roda gigi di blok iterator untuk memastikan bahwa pengecualian ditangani dengan benar.