Berkali-kali, saya melihatnya mengatakan bahwa menggunakan async
- await
tidak membuat utas tambahan. Itu tidak masuk akal karena satu-satunya cara agar komputer dapat melakukan lebih dari satu hal pada satu waktu adalah
- Sebenarnya melakukan lebih dari 1 hal dalam satu waktu (mengeksekusi secara paralel, memanfaatkan beberapa prosesor)
- Simulasi dengan menjadwalkan tugas dan beralih di antara mereka (lakukan sedikit A, sedikit B, sedikit A, dll.)
Jadi jika async
- await
tidak satu pun dari itu, lalu bagaimana bisa membuat aplikasi responsif? Jika hanya ada 1 utas, maka memanggil metode apa pun berarti menunggu metode selesai sebelum melakukan hal lain, dan metode di dalam metode tersebut harus menunggu hasilnya sebelum melanjutkan, dan seterusnya.
c#
.net
multithreading
asynchronous
async-await
Ms. Corlib
sumber
sumber
await
/async
bekerja tanpa membuat utas.Jawaban:
Sebenarnya, async / menunggu tidaklah ajaib. Topik lengkapnya cukup luas tetapi untuk jawaban yang cepat namun cukup lengkap untuk pertanyaan Anda, saya pikir kami bisa mengaturnya.
Mari kita atasi acara klik tombol sederhana di aplikasi Windows Forms:
Saya akan secara eksplisit tidak berbicara tentang apa pun
GetSomethingAsync
yang dikembalikan untuk saat ini. Anggap saja ini adalah sesuatu yang akan selesai setelah, katakanlah, 2 detik.Dalam dunia tradisional, non-asinkron, dunia, pengendali event klik tombol Anda akan terlihat seperti ini:
Ketika Anda mengklik tombol dalam formulir, aplikasi akan muncul membeku sekitar 2 detik, sementara kami menunggu metode ini selesai. Apa yang terjadi adalah bahwa "pompa pesan", pada dasarnya sebuah loop, diblokir.
Lingkaran ini terus-menerus bertanya kepada windows "Apakah ada yang melakukan sesuatu, seperti menggerakkan mouse, mengklik sesuatu? Apakah saya perlu mengecat ulang sesuatu? Jika demikian, beri tahu saya!" dan kemudian memproses "sesuatu" itu. Loop ini mendapat pesan bahwa pengguna mengklik "button1" (atau jenis pesan yang setara dari Windows), dan akhirnya memanggil
button1_Click
metode kami di atas. Sampai metode ini kembali, loop ini sekarang macet menunggu. Ini membutuhkan waktu 2 detik dan selama ini, tidak ada pesan yang sedang diproses.Sebagian besar hal yang berhubungan dengan windows dilakukan dengan menggunakan pesan, yang berarti bahwa jika loop pesan berhenti memompa pesan, bahkan hanya sesaat, itu dengan cepat dapat dilihat oleh pengguna. Misalnya, jika Anda memindahkan notepad atau program lain di atas program Anda sendiri, dan kemudian pergi lagi, sejumlah pesan cat dikirim ke program Anda yang menunjukkan wilayah jendela yang sekarang tiba-tiba menjadi terlihat lagi. Jika loop pesan yang memproses pesan-pesan ini sedang menunggu sesuatu, diblokir, maka tidak ada lukisan yang dilakukan.
Jadi, jika dalam contoh pertama,
async/await
tidak membuat utas baru, bagaimana cara melakukannya?Nah, yang terjadi adalah metode Anda dibagi menjadi dua. Ini adalah salah satu dari jenis topik yang luas sehingga saya tidak akan membahas terlalu banyak detail tetapi cukup untuk mengatakan metode ini dibagi menjadi dua hal ini:
await
, termasuk panggilan keGetSomethingAsync
await
Ilustrasi:
Ulang:
Pada dasarnya metode dijalankan seperti ini:
await
Itu memanggil
GetSomethingAsync
metode, yang melakukan hal itu, dan mengembalikan sesuatu yang akan menyelesaikan 2 detik di masa depanSejauh ini kita masih di dalam panggilan asli ke button1_Click, terjadi di utas utama, dipanggil dari loop pesan. Jika pembuatan kode
await
membutuhkan banyak waktu, UI akan tetap membeku. Dalam contoh kita, tidak banyakApa
await
kata kunci, bersama dengan beberapa sihir kompiler pintar, apakah itu pada dasarnya sesuatu seperti "Oke, Anda tahu apa, saya hanya akan kembali dari pengendali event klik tombol di sini. Ketika Anda (seperti, hal yang kami sedang menunggu) menyiasati untuk menyelesaikan, beri tahu saya karena saya masih memiliki beberapa kode tersisa untuk dieksekusi ".Sebenarnya itu akan membiarkan kelas SynchronizationContext tahu bahwa itu dilakukan, yang, tergantung pada konteks sinkronisasi aktual yang sedang dimainkan saat ini, akan mengantri untuk dieksekusi. Kelas konteks yang digunakan dalam program Windows Forms akan mengantri menggunakan antrian yang dipompa oleh loop pesan.
Jadi ia kembali ke loop pesan, yang sekarang bebas untuk terus memompa pesan, seperti memindahkan jendela, mengubah ukurannya, atau mengklik tombol lain.
Untuk pengguna, UI sekarang responsif lagi, memproses klik tombol lainnya, mengubah ukuran dan yang paling penting, menggambar ulang , sehingga tampaknya tidak membeku.
await
dan terus menjalankan sisa metode. Perhatikan bahwa kode ini sekali lagi dipanggil dari loop pesan sehingga jika kode ini melakukan sesuatu yang panjang tanpa menggunakanasync/await
dengan benar, itu lagi akan memblokir loop pesanAda banyak bagian yang bergerak di bawah tenda di sini jadi di sini ada beberapa tautan ke informasi lebih lanjut, saya akan mengatakan "jika Anda membutuhkannya", tetapi topik ini cukup luas dan cukup penting untuk mengetahui beberapa bagian yang bergerak itu . Selalu Anda akan mengerti bahwa async / menunggu masih konsep yang bocor. Beberapa batasan dan masalah mendasar masih bocor ke kode di sekitarnya, dan jika tidak, Anda biasanya harus men-debug aplikasi yang rusak secara acak karena tampaknya tidak ada alasan yang bagus.
OK, jadi bagaimana jika
GetSomethingAsync
memutar utas yang akan selesai dalam 2 detik? Ya, maka jelas ada utas baru dalam permainan. Namun, utas ini bukan karena async-ness metode ini, melainkan karena pemrogram metode ini memilih utas untuk mengimplementasikan kode asinkron. Hampir semua I / O tidak sinkron tidak menggunakan utas, mereka menggunakan hal-hal yang berbeda.async/await
sendiri tidak memunculkan thread baru tapi jelas "hal-hal yang kita tunggu" dapat diimplementasikan menggunakan thread.Ada banyak hal di .NET yang tidak selalu memutar utas sendiri tetapi masih asinkron:
SomethingSomethingAsync
atauBeginSomething
danEndSomething
dan ada yangIAsyncResult
terlibat.Biasanya hal-hal ini tidak menggunakan utas di bawah tenda.
OK, jadi Anda ingin beberapa "topik luas"?
Baiklah, mari kita tanyakan pada Try Roslyn tentang klik tombol kita:
Coba Roslyn
Saya tidak akan menautkan kelas yang dibuat penuh di sini, tetapi ini adalah hal yang sangat mengerikan.
sumber
await
dapat digunakan seperti yang Anda gambarkan juga, tetapi umumnya tidak. Hanya panggilan balik yang dijadwalkan (di kumpulan utas) - antara panggilan balik dan permintaan, tidak ada utas yang diperlukan.Saya jelaskan secara lengkap di posting blog saya There No No Thread .
Singkatnya, sistem I / O modern banyak menggunakan DMA (Direct Memory Access). Ada prosesor khusus yang didedikasikan untuk kartu jaringan, kartu video, pengontrol HDD, port serial / paralel, dll. Prosesor ini memiliki akses langsung ke bus memori, dan menangani pembacaan / penulisan sepenuhnya terlepas dari CPU. CPU hanya perlu memberi tahu perangkat lokasi di memori yang berisi data, dan kemudian dapat melakukan hal itu sendiri hingga perangkat menimbulkan interupsi yang memberitahukan CPU bahwa proses baca / tulis selesai.
Setelah operasi dalam penerbangan, tidak ada pekerjaan untuk CPU lakukan, dan dengan demikian tidak ada utas.
sumber
Task.Run
paling tepat untuk tindakan yang terikat CPU , tetapi memiliki beberapa kegunaan lain juga.Bukannya yang menunggu tidak keduanya . Ingat, tujuannya
await
bukan untuk membuat kode sinkron secara asinkron . Ini untuk mengaktifkan menggunakan teknik yang sama yang kita gunakan untuk menulis kode sinkron ketika memanggil kode asinkron . Menunggu adalah tentang membuat kode yang menggunakan operasi latensi tinggi terlihat seperti kode yang menggunakan operasi latensi rendah . Operasi latensi tinggi itu mungkin berada di utas, mereka mungkin pada perangkat keras tujuan khusus, mereka mungkin merobek pekerjaan mereka menjadi potongan-potongan kecil dan memasukkannya ke antrian pesan untuk diproses oleh utas UI nanti. Mereka melakukan sesuatu untuk mencapai sinkronisasi, tetapi merekaadalah orang-orang yang melakukannya. Menunggu hanya memungkinkan Anda mengambil keuntungan dari sinkronisasi itu.Juga, saya pikir Anda kehilangan opsi ketiga. Kami orang tua - anak-anak hari ini dengan musik rap mereka harus turun dari halaman saya, dll - ingat dunia Windows pada awal 1990-an. Tidak ada mesin multi-CPU dan tidak ada penjadwal thread. Anda ingin menjalankan dua aplikasi Windows secara bersamaan, Anda harus menghasilkan . Multitasking kooperatif . OS memberi tahu proses yang harus dijalankan, dan jika berperilaku buruk, OS akan membuat semua proses lainnya tidak dapat dilayani. Ini berjalan sampai menghasilkan, dan entah bagaimana ia harus tahu cara mengambil di mana ia tinggalkan saat berikutnya OS tangan mengontrol kembali ke sana. Kode asinkron single-threaded sangat mirip dengan itu, dengan "menunggu" alih-alih "menghasilkan". Menunggu berarti "Aku akan mengingat di mana aku pergi di sini, dan membiarkan orang lain lari sebentar; telepon aku kembali ketika tugas yang aku tunggu selesai, dan aku akan mengambil di tempat aku pergi." Saya pikir Anda dapat melihat bagaimana hal itu membuat aplikasi lebih responsif, seperti yang terjadi pada Windows 3 hari.
Ada kunci yang Anda lewatkan. Suatu metode dapat kembali sebelum kerjanya selesai . Itulah esensi asynchrony di sana. Metode mengembalikan, mengembalikan tugas yang berarti "pekerjaan ini sedang berlangsung; katakan padaku apa yang harus dilakukan ketika sudah selesai". Pekerjaan metode ini tidak dilakukan, meskipun telah kembali .
Sebelum operator menunggu, Anda harus menulis kode yang tampak seperti spaghetti yang diulir melalui keju swiss untuk menghadapi kenyataan bahwa kami memiliki pekerjaan yang harus dilakukan setelah selesai, tetapi dengan pengembalian dan penyelesaian yang tidak sinkron . Menunggu memungkinkan Anda untuk menulis kode yang tampak seperti pengembalian dan penyelesaian disinkronkan, tanpa mereka benar - benar disinkronkan.
sumber
yield
kata kunci. Keduaasync
metode dan iterator dalam C # adalah bentuk coroutine , yang merupakan istilah umum untuk fungsi yang tahu bagaimana menangguhkan operasinya saat ini untuk dilanjutkan kembali nanti. Sejumlah bahasa memiliki aliran coroutine atau kontrol seperti coroutine hari ini.Saya sangat senang seseorang mengajukan pertanyaan ini, karena untuk waktu yang lama saya juga percaya bahwa utas diperlukan untuk melakukan konkurensi. Ketika saya pertama kali melihat acara loop , saya pikir mereka bohong. Saya berpikir dalam hati, "tidak mungkin kode ini bisa bersamaan jika dijalankan dalam satu utas". Perlu diingat ini adalah setelah saya telah melalui perjuangan memahami perbedaan antara konkurensi dan paralelisme.
Setelah penelitian saya sendiri, saya akhirnya menemukan bagian yang hilang:
select()
. Secara khusus, IO multiplexing, dilaksanakan oleh berbagai kernel dengan nama yang berbeda:select()
,poll()
,epoll()
,kqueue()
. Ini adalah panggilan sistem yang, meskipun detail implementasi berbeda, memungkinkan Anda untuk melewatkan satu set deskriptor file untuk ditonton. Kemudian Anda dapat membuat panggilan lain yang memblokir hingga salah satu deskriptor file yang ditonton berubah.Dengan demikian, seseorang dapat menunggu di set acara IO (loop acara utama), menangani acara pertama yang selesai, dan kemudian menghasilkan kontrol kembali ke loop acara. Bilas dan ulangi.
Bagaimana cara kerjanya? Yah, jawaban singkatnya adalah kernel dan sulap tingkat hardware. Ada banyak komponen di komputer selain CPU, dan komponen ini dapat bekerja secara paralel. Kernel dapat mengontrol perangkat ini dan berkomunikasi langsung dengan mereka untuk menerima sinyal tertentu.
Panggilan sistem multiplexing IO ini adalah blok bangunan mendasar dari loop peristiwa single-threaded seperti node.js atau Tornado. Saat Anda
await
suatu fungsi, Anda menonton acara tertentu (penyelesaian fungsi itu), dan kemudian menghasilkan kontrol kembali ke loop acara utama. Ketika acara yang Anda tonton selesai, fungsi (akhirnya) mengambil dari tempat sebelumnya. Fungsi yang memungkinkan Anda untuk menunda dan melanjutkan perhitungan seperti ini disebut coroutine .sumber
await
danasync
gunakan Tugas bukan Utas.Kerangka kerja ini memiliki kumpulan utas yang siap untuk menjalankan beberapa pekerjaan dalam bentuk objek Tugas ; mengirimkan Tugas ke kumpulan berarti memilih utas 1 , yang sudah ada , untuk memanggil metode tindakan tugas. Membuat Tugas adalah masalah menciptakan objek baru, jauh lebih cepat daripada membuat utas baru.
Mengingat sebuah Tugas dimungkinkan untuk melampirkan sebuah Lanjutan padanya, itu adalah objek Tugas baru yang akan dieksekusi setelah utas berakhir.
Karena
async/await
menggunakan Tugas , mereka tidak membuat utas baru .Sementara teknik pemrograman interupsi digunakan secara luas di setiap OS modern, saya tidak berpikir mereka relevan di sini.
Anda dapat memiliki dua tugas berikatan CPU yang dijalankan secara paralel (sebenarnya disisipkan) dalam satu CPU yang digunakan
aysnc/await
.Itu tidak bisa dijelaskan hanya dengan fakta bahwa OS mendukung antrian IORP .
Terakhir kali saya memeriksa kompiler mengubah
async
metode menjadi DFA , pekerjaan dibagi menjadi beberapa langkah, masing-masing diakhiri denganawait
instruksi.The
await
dimulai nya Tugas dan melampirkannya kelanjutan untuk mengeksekusi langkah berikutnya.Sebagai contoh konsep, berikut adalah contoh kode pseudo.
Segala sesuatunya disederhanakan demi kejelasan dan karena saya tidak ingat persis semua detailnya.
Itu bisa berubah menjadi sesuatu seperti ini
1 Sebenarnya pool dapat memiliki kebijakan pembuatan tugasnya.
sumber
Saya tidak akan bersaing dengan Eric Lippert atau Lasse V. Karlsen, dan yang lainnya, saya hanya ingin menarik perhatian ke sisi lain dari pertanyaan ini, yang saya pikir tidak disebutkan secara eksplisit.
Menggunakannya
await
sendiri tidak membuat aplikasi Anda responsif secara ajaib. Jika apa pun yang Anda lakukan dalam metode yang Anda tunggu dari blok thread UI, itu masih akan memblokir UI Anda dengan cara yang sama seperti versi yang tidak ditunggu-tunggu .Anda harus menulis metode yang bisa Anda tunggu secara spesifik sehingga bisa menghasilkan thread baru atau menggunakan sesuatu seperti port penyelesaian (yang akan mengembalikan eksekusi di utas saat ini dan memanggil sesuatu yang lain untuk kelanjutan setiap kali port penyelesaian diberi sinyal). Tetapi bagian ini dijelaskan dengan baik dalam jawaban lain.
sumber
Inilah cara saya melihat semua ini, mungkin tidak terlalu akurat secara teknis tetapi ini membantu saya, setidaknya :).
Pada dasarnya ada dua jenis pemrosesan (perhitungan) yang terjadi pada mesin:
Jadi, ketika kita menulis sepotong kode sumber, setelah kompilasi, tergantung pada objek yang kita gunakan (dan ini sangat penting), pemrosesan akan terikat CPU , atau IO terikat , dan pada kenyataannya, itu dapat terikat pada kombinasi dari kedua.
Beberapa contoh:
FileStream
objek (yang merupakan Stream), pemrosesan akan dikatakan, 1% CPU terikat, dan 99% IO terikat.NetworkStream
objek (yang merupakan Stream), pemrosesan akan dikatakan, 1% CPU terikat, dan 99% IO terikat.Memorystream
objek (yang merupakan Stream), pemrosesan akan terikat CPU 100%.Jadi, seperti yang Anda lihat, dari sudut pandang programmer berorientasi objek, meskipun saya selalu mengakses
Stream
objek, apa yang terjadi di bawah ini mungkin sangat bergantung pada jenis objek.Sekarang, untuk mengoptimalkan berbagai hal, terkadang berguna untuk dapat menjalankan kode secara paralel (perhatikan bahwa saya tidak menggunakan kata asinkron) jika memungkinkan dan / atau perlu.
Beberapa contoh:
Sebelum async / menunggu, pada dasarnya kami memiliki dua solusi untuk ini:
Async / menunggu hanya model pemrograman yang umum, berdasarkan pada konsep Tugas . Ini sedikit lebih mudah digunakan daripada utas atau kumpulan utas untuk tugas yang terikat CPU, dan jauh lebih mudah digunakan daripada model Begin / End yang lama. Undercover, bagaimanapun, "hanya" merupakan pembungkus penuh fitur super canggih pada keduanya.
Jadi, kemenangan sebenarnya sebagian besar pada tugas-tugas IO Bound , tugas yang tidak menggunakan CPU, tetapi async / menunggu masih hanya model pemrograman, itu tidak membantu Anda untuk menentukan bagaimana / di mana pemrosesan akan terjadi pada akhirnya.
Itu berarti itu bukan karena kelas memiliki metode "DoSomethingAsync" mengembalikan objek Tugas yang dapat Anda anggap itu terikat CPU (yang berarti mungkin sangat tidak berguna , terutama jika tidak memiliki parameter token pembatalan), atau IO Bound (yang berarti itu mungkin suatu keharusan ), atau kombinasi keduanya (karena modelnya cukup viral, ikatan dan manfaat potensial dapat, pada akhirnya, super campuran dan tidak begitu jelas).
Jadi, kembali ke contoh saya, melakukan operasi Write saya menggunakan async / menunggu di MemoryStream akan tetap terikat CPU (saya mungkin tidak akan mendapat manfaat dari itu), meskipun saya pasti akan mendapat manfaat dari itu dengan file dan aliran jaringan.
sumber
Meringkas jawaban lain:
Async / await terutama dibuat untuk tugas-tugas terikat IO karena dengan menggunakannya, orang dapat menghindari memblokir utas panggilan. Penggunaan utama mereka adalah dengan utas UI yang tidak diinginkan agar utas diblokir pada operasi terikat IO.
Async tidak membuat utas sendiri. Utas metode pemanggilan digunakan untuk menjalankan metode async hingga menemukan metode yang ditunggu. Utas yang sama kemudian melanjutkan untuk menjalankan sisa metode pemanggilan di luar pemanggilan metode async. Dalam metode async yang disebut, setelah kembali dari yang ditunggu, kelanjutan dapat dieksekusi pada utas dari kumpulan utas - satu-satunya tempat utas terpisah muncul dalam gambar.
sumber
Saya coba jelaskan dari bawah. Mungkin seseorang merasa terbantu. Saya ada di sana, melakukan itu, menciptakannya kembali, ketika membuat game sederhana dalam DOS di Pascal (masa lalu yang baik ...)
Jadi ... Dalam setiap aplikasi yang digerakkan oleh peristiwa, memiliki loop acara di dalamnya yang seperti ini:
Kerangka kerja biasanya menyembunyikan detail ini dari Anda tetapi ada di sana. Fungsi getMessage membaca acara berikutnya dari antrian acara atau menunggu hingga terjadi peristiwa: gerakan mouse, keydown, keyup, klik, dll. Dan kemudian dispatchMessage mengirimkan acara ke pengendali acara yang sesuai. Kemudian tunggu acara berikutnya dan seterusnya hingga acara berhenti muncul yang keluar dari loop dan menyelesaikan aplikasi.
Penangan acara harus berjalan cepat sehingga loop acara dapat melakukan polling untuk lebih banyak acara dan UI tetap responsif. Apa yang terjadi jika klik tombol memicu operasi mahal seperti ini?
Nah, UI menjadi tidak responsif sampai operasi 10 detik selesai saat kontrol tetap berada dalam fungsi. Untuk mengatasi masalah ini, Anda perlu memecah tugas menjadi beberapa bagian kecil yang dapat dieksekusi dengan cepat. Ini berarti Anda tidak dapat menangani semuanya dalam satu peristiwa. Anda harus melakukan sebagian kecil dari pekerjaan, kemudian memposting acara lain ke antrian acara untuk meminta kelanjutan.
Jadi, Anda akan mengubahnya menjadi:
Dalam hal ini hanya iterasi pertama yang berjalan, maka ia memposting pesan ke antrian acara untuk menjalankan iterasi berikutnya dan kembali. Ini contoh
postFunctionCallMessage
fungsi pseudo kami menempatkan acara "panggil fungsi ini" ke antrian, sehingga pengirim acara akan memanggilnya saat sudah mencapai itu. Ini memungkinkan semua acara GUI lainnya diproses sementara terus menjalankan karya yang sudah berjalan lama.Selama tugas berjalan yang panjang ini berjalan, acara kelanjutannya selalu dalam antrian acara. Jadi pada dasarnya Anda menemukan penjadwal tugas Anda sendiri. Di mana acara kelanjutan dalam antrian adalah "proses" yang sedang berjalan. Sebenarnya ini yang dilakukan sistem operasi, kecuali bahwa pengiriman peristiwa kelanjutan dan kembali ke loop scheduler dilakukan melalui penghitung waktu CPU di mana OS mendaftarkan kode switching konteks, jadi Anda tidak perlu peduli tentang hal itu. Tapi di sini Anda menulis penjadwal Anda sendiri sehingga Anda perlu peduli tentang hal itu - sejauh ini.
Jadi kita dapat menjalankan tugas yang berjalan lama dalam satu utas paralel dengan GUI dengan memecahnya menjadi potongan-potongan kecil dan mengirimkan acara lanjutan. Ini adalah ide umum dari
Task
kelas. Ini merupakan bagian dari pekerjaan dan ketika Anda memanggilnya.ContinueWith
, Anda menentukan fungsi apa yang disebut sebagai bagian berikutnya ketika bagian saat ini selesai (dan nilai pengembaliannya diteruskan ke kelanjutan). TheTask
kelas menggunakan kolam thread, di mana ada loop acara di setiap thread menunggu untuk melakukan potongan-potongan kerja sama dengan ingin saya menunjukkan di awal. Dengan cara ini Anda dapat memiliki jutaan tugas yang berjalan secara paralel, tetapi hanya beberapa utas untuk menjalankannya. Tetapi itu akan bekerja dengan baik hanya dengan satu utas - selama tugas Anda dibagi dengan baik menjadi beberapa bagian, masing-masing dari mereka tampak berjalan dalam parellel.Tetapi melakukan semua ini chaining membagi pekerjaan menjadi potongan-potongan kecil secara manual adalah pekerjaan yang rumit dan benar-benar mengacaukan tata letak logika, karena seluruh kode tugas latar belakang pada dasarnya
.ContinueWith
berantakan. Jadi di sinilah kompiler membantu Anda. Ini melakukan semua rangkaian dan kelanjutan untuk Anda di latar belakang. Ketika Anda mengatakanawait
Anda memberi tahu kompiler bahwa "berhenti di sini, tambahkan sisa fungsi sebagai tugas lanjutan". Kompilator menangani sisanya, jadi Anda tidak perlu melakukannya.sumber
Sebenarnya,
async await
rantai adalah mesin negara yang dihasilkan oleh kompiler CLR.async await
Namun tidak menggunakan utas yang TPL menggunakan utas untuk menjalankan tugas.Alasan aplikasi tidak diblokir adalah bahwa mesin negara dapat memutuskan co-rutin mana yang akan dieksekusi, ulangi, periksa, dan putuskan lagi.
Bacaan lebih lanjut:
Apa yang dihasilkan async & menunggu?
Async Tunggu dan Generated StateMachine
Asinkron C # dan F # (III.): Bagaimana cara kerjanya? - Tomas Petricek
Edit :
Baik. Sepertinya uraian saya salah. Namun saya harus menunjukkan bahwa mesin negara adalah aset penting bagi
async await
. Sekalipun Anda menerima I / O asinkron, Anda masih memerlukan penolong untuk memeriksa apakah operasi telah selesai karena itu kami masih memerlukan mesin keadaan dan menentukan rutin mana yang dapat dijalankan secara bersamaan secara serempak.sumber
Ini tidak secara langsung menjawab pertanyaan, tetapi saya pikir ini adalah informasi tambahan interessting:
Async dan menunggu tidak membuat utas baru dengan sendirinya. TETAPI tergantung pada tempat Anda menggunakan async menunggu, bagian sinkron SEBELUM menunggu menunggu berjalan pada utas berbeda dari bagian sinkron SETELAH menunggu (misalnya ASP.NET dan ASP.NET inti berperilaku berbeda).
Dalam aplikasi berbasis UI-Thread (WinForms, WPF) Anda akan berada di utas yang sama sebelum dan sesudah. Tetapi ketika Anda menggunakan async jauh pada utas pool Thread, utas sebelum dan sesudah menunggu mungkin tidak sama.
Video hebat tentang topik ini
sumber