Memburamkan garis antara fungsi async dan fungsi biasa di C # 5.0

10

Akhir-akhir ini saya sepertinya tidak bisa mendapatkan cukup dari pola async-wait yang menakjubkan dari C # 5.0. Di mana saja kau selama hidup saya?

Saya benar-benar senang dengan sintaks yang sederhana, tetapi saya mengalami satu kesulitan kecil. Masalah saya adalah bahwa fungsi-fungsi async memiliki deklarasi yang sama sekali berbeda dari fungsi-fungsi biasa. Karena hanya fungsi-fungsi async yang dapat menunggu pada fungsi-fungsi async lainnya, ketika saya mencoba untuk mem-porting beberapa kode pemblokiran lama ke async, saya mempunyai efek domino dari fungsi-fungsi yang harus saya konversi.

Orang-orang menyebut ini sebagai serangan zombie . Saat async menggigit kode Anda, kode itu akan terus bertambah besar. Proses porting tidak sulit, hanya membuang asyncdeklarasi dan membungkus nilai pengembaliannya Task<>. Tapi itu menjengkelkan untuk melakukan ini berulang-ulang ketika porting kode sinkron lama.

Sepertinya saya akan jauh lebih alami jika kedua tipe fungsi (async dan sinkronisasi lama polos) memiliki sintaks yang sama persis. Jika ini masalahnya, porting akan mengambil upaya nol dan saya bisa beralih tanpa rasa sakit di antara dua bentuk.

Saya pikir ini bisa berhasil jika kita mengikuti aturan ini:

  1. Fungsi-fungsi Async tidak membutuhkan asyncdeklarasi lagi. Jenis kembali mereka tidak harus dibungkus Task<>. Compiler akan mengidentifikasi fungsi async selama kompilasi dengan sendirinya dan melakukan pembungkus Tugas <> secara otomatis sesuai kebutuhan.

  2. Tidak ada lagi panggilan api dan lupa fungsi async. Jika Anda ingin memanggil fungsi async, Anda harus menunggu di sana. Saya jarang menggunakan api-dan-lupa, dan semua contoh kondisi balapan gila atau kebuntuan selalu tampaknya didasarkan pada mereka. Saya pikir mereka terlalu membingungkan dan "tidak terhubung" dengan pola pikir yang sinkron yang kami coba utarakan.

  3. Jika Anda benar-benar tidak dapat hidup tanpa api-dan-lupa, akan ada sintaks khusus untuk itu. Bagaimanapun, itu tidak akan menjadi bagian dari sintaksis terpadu sederhana yang saya bicarakan.

  4. Satu-satunya kata kunci yang Anda perlukan untuk menunjukkan panggilan asinkron adalah await. Jika Anda menunggu, panggilan tidak sinkron. Jika tidak, panggilannya adalah sinkron lama (ingat, kami tidak punya api dan lupakan lagi).

  5. Compiler akan mengidentifikasi fungsi-fungsi async secara otomatis (karena mereka tidak memiliki deklarasi khusus lagi). Aturan 4 membuat ini sangat mudah dilakukan - jika suatu fungsi memiliki awaitpanggilan di dalamnya, itu async.

Bisakah ini bekerja? atau aku melewatkan sesuatu? Sintaksis terpadu ini jauh lebih lancar dan bisa menyelesaikan infestasi zombie sama sekali.

Beberapa contoh:

// assume this is an async function (has await calls inside)
int CalcRemoteBalanceAsync() { ... }

// assume this is a regular sync function (has no await calls inside)
int CalcRemoteBalance() { ... }

// now let's try all combinations and see what they do:

// this is the common synchronous case - it will block
int b1 = CalcRemoteBalance();

// this is the common asynchronous case - it will not block
int b2 = await CalcRemoteBalanceAsync();

// choosing to call an asynchronous function in a synchronous manner - it will block
// this syntax was used previously for async fire-and-forget, but now it's just synchronous
int b3 = CalcRemoteBalanceAsync();

// strange combination - it will block since it's calling a synchronous function
// it should probably show a compilation warning though
int b4 = await CalcRemoteBalance();

Catatan: ini merupakan kelanjutan dari diskusi terkait yang menarik di SO

Talkol
sumber
3
Anda selalu menunggu operasi asinkron Anda? Tolong katakan padaku Anda tidak melakukan ini segera setelah memecat mereka ...
Jimmy Hoffa
1
Juga, salah satu hal hebat tentang async adalah Anda tidak harus awaitsegera melakukannya. Anda dapat melakukan sesuatu seperti var task = FooAsync(); Bar(); await task;. Bagaimana saya melakukan ini dalam proposal Anda?
svick
3
SO sedang berdiskusi? Di mana BFG-3000 saya ...
Robert Harvey
2
@talkol Anda berpikir pemrograman paralel itu cabul? Itu pandangan yang menarik, untuk sedikitnya, ketika Anda berbicara tentang async. Saya pikir itu salah satu keuntungan besar dari async- await: bahwa ini memungkinkan Anda untuk dengan mudah menyusun operasi asinkron (dan tidak hanya dengan cara "mulai A, tunggu A, mulai B, tunggu B" yang paling sederhana). Dan sudah ada sintaks khusus persis untuk tujuan ini: itu disebut await.
svick
1
@vick haha, sekarang kita sudah pergi ke sana :) Saya tidak berpikir prog paralel adalah cabul, tapi saya pikir melakukannya dengan async-tunggu adalah. Async-wait adalah sintaksis gula untuk menjaga keadaan pikiran sinkron Anda tanpa membayar harga pemblokiran. Jika Anda sudah berpikir secara paralel, saya akan mendorong Anda untuk menggunakan pola yang berbeda
talkol

Jawaban:

9

Pertanyaan Anda sudah dijawab dalam pertanyaan SO yang Anda tautkan.

Tujuan dari async / menunggu adalah untuk membuatnya lebih mudah untuk menulis kode di dunia dengan banyak operasi latensi tinggi. Sebagian besar operasi Anda bukan latensi tinggi.

Ketika WinRT pertama kali keluar, para desainer menggambarkan bagaimana mereka memutuskan operasi mana yang akan menjadi async. Mereka memutuskan bahwa apa pun yang akan mengambil 50 ms atau lebih akan async, dan sisa metode akan menjadi metode biasa, non-asinkron.

Berapa banyak metode yang harus ditulis ulang agar tidak sinkron? Sekitar 10 persen dari mereka. 90% lainnya tidak terpengaruh sama sekali.

Eric Lippert kemudian menjelaskan secara rinci teknis yang cukup besar mengapa mereka memilih untuk tidak mengambil pendekatan satu ukuran untuk semua. Dia pada dasarnya mengatakan itu asyncdan awaitmerupakan implementasi parsial dari gaya kelanjutan-lewat, dan mengoptimalkan gaya seperti itu untuk memenuhi semua kasus adalah masalah yang sulit.

Robert Harvey
sumber
Harap perhatikan perbedaan substansial antara pertanyaan SO dan yang ini. SO bertanya mengapa tidak membuat semuanya async. Di sini kami tidak menyarankan itu, kami menyarankan membuat 10% async, hanya menggunakan sintaks yang sama untuk itu saja. Menggunakan sintaks yang lebih dekat memiliki keuntungan bahwa Anda dapat lebih mudah mengubah mana 10% async, tanpa menderita efek domino dari perubahan ini
talkol
Saya sedikit tidak jelas tentang mengapa asyncakan menghasilkan infestasi zombie. Bahkan jika suatu metode memanggil 10 metode lain, bukankah Anda hanya perlu yang asynctingkat atas?
Robert Harvey
6
Katakanlah 100% kode saya saat ini disinkronkan. Sekarang saya memiliki fungsi internal level daun tunggal yang menanyakan DB yang ingin saya ubah menjadi async. Sekarang untuk menjadikannya async, saya perlu peneleponnya menjadi async, dan peneleponnya menjadi async, dan sebagainya hingga tingkat atas. Tentu saja saya sedang berbicara tentang kasus di mana seluruh rantai ditunggu (untuk menjaga desain sinkron kode atau lulus kembali)
talkol