Mengapa kita memerlukan kata kunci async?


19

Saya baru saja mulai bermain-main dengan async / menunggu di .Net 4.5. Satu hal yang saya awalnya ingin tahu, mengapa kata kunci async diperlukan? Penjelasan yang saya baca adalah bahwa itu adalah penanda sehingga kompiler tahu metode menunggu sesuatu. Tapi sepertinya kompiler harus bisa mengetahui ini tanpa kata kunci. Jadi apa lagi fungsinya?


2
Berikut ini adalah artikel (seri) blog yang sangat bagus yang membahas beberapa detail yang menggambarkan kata kunci dalam C # dan fungsionalitas terkait dalam F #.
paul

Saya pikir ini terutama penanda untuk pengembang, dan bukan ke kompiler.
CodesInChaos

8
Artikel ini menjelaskan hal itu: blogs.msdn.com/b/ericlippert/archive/2010/11/11/...
svick

@vick terima kasih, ini yang saya cari. Masuk akal sekarang.
ConditionRacer

Jawaban:


23

Ada beberapa jawaban di sini, dan semuanya berbicara tentang apa yang dilakukan metode async, tetapi tidak satupun dari mereka menjawab pertanyaan, itulah mengapa asyncdiperlukan sebagai kata kunci yang masuk dalam deklarasi fungsi.

Ini bukan "untuk mengarahkan kompiler untuk mengubah fungsi dengan cara khusus"; awaitsendirian bisa melakukan itu. Mengapa? Karena C # sudah memiliki mekanisme lain di mana kehadiran kata kunci khusus dalam tubuh metode menyebabkan compiler untuk melakukan ekstrim (dan sangat mirip dengan async/await) transformasi pada tubuh metode: yield.

Kecuali itu yieldbukan kata kunci sendiri dalam C #, dan memahami mengapa akan menjelaskan asyncjuga. Tidak seperti di sebagian besar bahasa yang mendukung mekanisme ini, di C # Anda tidak bisa mengatakan yield value;Anda harus mengatakannya yield return value;. Mengapa? Karena itu ditambahkan ke bahasa setelah C # sudah ada, dan itu cukup masuk akal untuk mengasumsikan bahwa seseorang, di suatu tempat, mungkin telah digunakan yieldsebagai nama variabel. Tetapi karena tidak ada skenario yang sudah ada sebelumnya yang <variable name> returnsecara sintaksis benar, yield returnditambahkan ke bahasa untuk memungkinkan untuk memperkenalkan generator sambil mempertahankan kompatibilitas mundur 100% dengan kode yang ada.

Dan inilah mengapa asyncditambahkan sebagai pengubah fungsi: untuk menghindari kerusakan kode yang ada yang digunakan awaitsebagai nama variabel. Karena tidak ada asyncmetode yang sudah ada, tidak ada kode lama yang tidak valid, dan dalam kode baru, kompiler dapat menggunakan keberadaan asynctag untuk mengetahui yang awaitharus diperlakukan sebagai kata kunci dan bukan pengidentifikasi.


3
Ini cukup banyak apa yang dikatakan artikel ini juga (awalnya diposting oleh @svick di komentar), tetapi tidak ada yang pernah mempostingnya sebagai jawaban. Hanya butuh dua setengah tahun! Terima kasih :)
ConditionRacer

Bukankah itu berarti bahwa dalam beberapa versi mendatang, persyaratan untuk menentukan asynckata kunci dapat dibatalkan? Jelas itu akan menjadi terobosan BC tetapi dapat dianggap mempengaruhi sangat sedikit proyek dan menjadi persyaratan langsung untuk meningkatkan (yaitu mengubah nama variabel).
Pertanyaan Quolonel

6

itu mengubah metode dari metode normal ke objek dengan panggilan balik yang memerlukan pendekatan yang sama sekali berbeda untuk pembuatan kode

dan ketika sesuatu yang drastis seperti itu terjadi adalah kebiasaan untuk menandakannya dengan jelas (kami belajar pelajaran itu dari C ++)


Jadi ini benar-benar sesuatu untuk pembaca / programmer, dan tidak terlalu banyak untuk kompiler?
ConditionRacer

@ Justin984 itu pasti membantu untuk memberi sinyal ke kompiler sesuatu yang perlu terjadi
ratchet freak

Saya rasa saya ingin tahu mengapa kompiler membutuhkannya. Tidak bisakah itu hanya menguraikan metode dan melihat bahwa menunggu ada di suatu tempat di tubuh metode?
ConditionRacer

ini membantu ketika Anda ingin menjadwalkan beberapa asyncs pada saat yang sama sehingga mereka tidak menjalankan satu demi satu tetapi secara bersamaan (jika utas tersedia setidaknya)
ratchet freak

@ Justin984: Tidak setiap metode kembali Task<T>sebenarnya memiliki karakteristik sebuah asyncmetode - misalnya, Anda mungkin hanya berjalan melalui beberapa logika bisnis / pabrik dan kemudian mendelegasikan beberapa lainnya metode kembali Task<T>. asyncmetode memiliki tipe Task<T>pengembalian tanda tangan tetapi tidak benar - benar mengembalikan Task<T>, mereka hanya mengembalikan a T. Untuk mengetahui semua ini tanpa asynckata kunci, kompiler C # harus melakukan segala macam pemeriksaan mendalam terhadap metode ini, yang mungkin akan memperlambatnya sedikit, dan menyebabkan segala macam ambiguitas.
Aaronaught

4

Seluruh gagasan dengan kata kunci seperti "async" atau "tidak aman" adalah untuk menghapus ambiguitas tentang bagaimana kode yang mereka modifikasi harus diperlakukan. Dalam kasus kata kunci async, ia memberitahu kompiler untuk memperlakukan metode yang dimodifikasi sebagai sesuatu yang tidak perlu kembali segera. Ini memungkinkan utas tempat metode ini digunakan untuk melanjutkan tanpa harus menunggu hasil dari metode itu. Ini secara efektif optimasi kode.


Saya tergoda untuk melakukan downvote karena meskipun paragraf pertama benar, perbandingan dengan interupsi tidak benar (menurut pendapat saya).
paul

Saya melihat mereka agak analog dalam hal tujuan tetapi saya akan menghapusnya demi kejelasan.
Insinyur Dunia

Interupsi lebih seperti peristiwa daripada kode async dalam pikiran saya - mereka menyebabkan perubahan konteks dan berjalan sampai selesai sebelum kode normal dilanjutkan (dan kode normal mungkin sama sekali tidak tahu bagaimana ia berjalan juga), sedangkan dengan kode async metode ini mungkin belum selesai sebelum utas panggilan dilanjutkan. Saya melihat apa yang Anda coba katakan melalui mekanisme berbeda yang membutuhkan implementasi berbeda di bawah tenda, tetapi interupsi tidak cocok dengan saya untuk menggambarkan hal ini. (+1 untuk sisanya, btw.)
paul

0

OK, ini saya ambil.

Ada sesuatu yang disebut coroutine yang telah dikenal selama beberapa dekade. ("Knuth and Hopper" -class "selama beberapa dekade") Mereka adalah generalisasi dari subrutin , sedemikian rupa sehingga mereka tidak hanya mendapatkan dan melepaskan kontrol pada pernyataan fungsi mulai dan kembali, tetapi mereka juga melakukannya pada titik tertentu ( titik suspensi ). Subrutin adalah coroutine tanpa titik suspensi.

Mereka adalah PLAIN MUDAH untuk menerapkan dengan makro C, seperti yang ditunjukkan dalam makalah berikut tentang "protothreads". ( http://dunkels.com/adam/dunkels06protothreads.pdf ) Baca. Aku akan menunggu...

Intinya adalah bahwa makro membuat label besar switch, dan casepada setiap titik suspensi. Pada setiap titik penangguhan, fungsi menyimpan nilai caselabel segera berikut , sehingga ia tahu di mana untuk melanjutkan eksekusi pada saat dipanggil. Dan itu mengembalikan kontrol ke penelepon.

Ini dilakukan tanpa mengubah aliran kontrol kode yang dijelaskan dalam "protothread".

Bayangkan sekarang bahwa Anda memiliki lingkaran besar yang memanggil semua "protothreads" ini pada gilirannya, dan Anda secara bersamaan menjalankan "protothreads" pada satu utas.

Pendekatan ini memiliki dua kelemahan:

  1. Anda tidak dapat menyimpan status dalam variabel lokal di antara pengulangan.
  2. Anda tidak dapat menangguhkan "protothread" dari kedalaman panggilan yang sewenang-wenang. (semua titik suspensi harus pada level 0)

Ada beberapa solusi untuk keduanya:

  1. Semua variabel lokal harus ditarik ke konteks protothread (konteks yang sudah dibutuhkan oleh fakta bahwa protothread harus menyimpan titik kembalinya berikutnya)
  2. Jika Anda merasa Anda benar-benar perlu memanggil protothread lain dari protothread, "bibit" protothread anak dan tunda sampai selesai anak.

Dan jika Anda memiliki dukungan kompiler untuk melakukan pekerjaan menulis ulang yang dilakukan oleh makro dan solusi, well, Anda bisa menulis kode protothread Anda seperti yang Anda inginkan dan memasukkan titik-titik suspensi dengan kata kunci.

Dan ini adalah apa asyncdan awaitsemua tentang: membuat coroutine (stackless).

Coroutine di C # diverifikasi sebagai objek dari kelas (generik atau non-generik) Task.

Saya menemukan kata kunci ini sangat menyesatkan. Bacaan mental saya adalah:

  • async sebagai "suspensible"
  • await sebagai "tunda sampai selesai"
  • Task sebagai "masa depan ..."

Sekarang. Apakah kita benar-benar perlu menandai fungsinya async? Selain mengatakan bahwa itu harus memicu mekanisme penulisan ulang kode untuk membuat fungsi menjadi coroutine, itu menyelesaikan beberapa ambiguitas. Pertimbangkan kode ini.

public Task<object> AmIACoroutine() {
    var tcs = new TaskCompletionSource<object>();
    return tcs.Task;
}

Dengan asumsi itu asynctidak wajib, apakah ini coroutine atau fungsi normal? Haruskah kompiler menulis ulang sebagai coroutine atau tidak? Keduanya bisa dimungkinkan dengan semantik akhirnya yang berbeda.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.