Pertanyaan ini menyangkut bahasa C #, tetapi saya berharap untuk mencakup bahasa lain seperti Java atau TypeScript.
Microsoft merekomendasikan praktik terbaik dalam menggunakan panggilan asinkron di .NET. Di antara rekomendasi ini, mari kita pilih dua:
- ubah tanda tangan dari metode async sehingga mereka mengembalikan Task atau Task <> (dalam TypeScript, itu akan menjadi Janji <>)
- ubah nama metode async menjadi diakhiri dengan xxxAsync ()
Sekarang, ketika mengganti komponen sinkron level rendah dengan async, ini berdampak pada tumpukan penuh aplikasi. Karena async / menunggu memiliki dampak positif hanya jika digunakan "all up up", itu berarti tanda tangan dan nama metode setiap lapisan dalam aplikasi harus diubah.
Arsitektur yang baik sering melibatkan penempatan abstraksi di antara setiap lapisan, sehingga mengganti komponen tingkat rendah dengan yang lain tidak terlihat oleh komponen tingkat atas. Dalam C #, abstraksi mengambil bentuk antarmuka. Jika kami memperkenalkan komponen baru, level rendah, async, setiap antarmuka dalam tumpukan panggilan perlu dimodifikasi atau diganti oleh antarmuka baru. Cara masalah dipecahkan (async atau sinkronisasi) di kelas pelaksana tidak tersembunyi (abstrak) untuk penelepon lagi. Penelepon harus tahu apakah itu sinkron atau asinkron.
Bukankah async / menunggu praktik terbaik yang bertentangan dengan prinsip "arsitektur yang baik"?
Apakah ini berarti bahwa setiap antarmuka (katakanlah IEnumerable, IDataAccessLayer) memerlukan rekanan async mereka (IAsyncEnumerable, IAsyncDataAccessLayer) sedemikian rupa sehingga dapat diganti dalam tumpukan saat beralih ke dependensi async?
Jika kita mendorong masalah sedikit lebih jauh, bukankah akan lebih mudah untuk menganggap setiap metode sebagai async (untuk mengembalikan Tugas <> atau Janji <>), dan untuk metode untuk menyinkronkan panggilan async ketika sebenarnya tidak async? Apakah ini sesuatu yang diharapkan dari bahasa pemrograman masa depan?
sumber
CancellationToken
, dan mereka yang memang ingin menyediakan default). Menghapus metode sinkronisasi yang ada (dan secara proaktif memecah semua kode) jelas bukan starter.Jawaban:
Apa Warna Fungsi Anda?
Anda mungkin tertarik dengan Bob Nystrom's What Color Is Your Function 1 .
Dalam artikel ini, ia menggambarkan bahasa fiksi di mana:
Meskipun fiktif, ini terjadi cukup teratur dalam bahasa pemrograman:
this
.Seperti yang Anda sadari, karena aturan ini, fungsi merah cenderung menyebar di sekitar basis kode. Anda memasukkan satu, dan sedikit demi sedikit itu menjajah seluruh basis kode.
1 Bob Nystrom, selain ngeblog, juga bagian dari tim Dart dan telah menulis seri Crafting Interpreters kecil ini; sangat direkomendasikan untuk bahasa pemrograman / compiler apa pun.
2 Tidak sepenuhnya benar, karena Anda dapat memanggil fungsi async dan memblokirnya sampai kembali, tetapi ...
Batasan Bahasa
Ini pada dasarnya adalah batasan bahasa / run-time.
Bahasa dengan M: N threading, misalnya, seperti Erlang dan Go, tidak memiliki
async
fungsi: setiap fungsi berpotensi async dan "serat" -nya hanya akan ditangguhkan, ditukar, dan ditukar kembali ketika sudah siap lagi.C # menggunakan model threading 1: 1, dan oleh karena itu memutuskan untuk memunculkan sinkronisitas dalam bahasa untuk menghindari pemblokiran utas secara tidak sengaja.
Di hadapan keterbatasan bahasa, pedoman pengkodean harus beradaptasi.
sumber
async
); memang, itu adalah pola yang cukup umum untuk membangun UI responsif. Apakah itu secantik Erlang? Nggak. Tapi itu masih jauh dari C :)Anda benar ada kontradiksi di sini, tetapi bukan "praktik terbaik" yang buruk. Itu karena fungsi asynchronous pada dasarnya berbeda dari yang sinkron. Alih-alih menunggu hasil dari dependensinya (biasanya beberapa IO) itu menciptakan tugas yang akan ditangani oleh loop acara utama. Ini bukan perbedaan yang bisa disembunyikan dengan baik di bawah abstraksi.
sumber
async
metode dalam C # untuk memberikan kontrak sinkron juga, jika Anda mau. Satu-satunya perbedaan adalah bahwa Go asinkron secara default sedangkan C # sinkron secara default.async
memberi Anda model pemrograman kedua -async
adalah abstraksi (apa yang sebenarnya ia lakukan tergantung pada runtime, penjadwal tugas, konteks sinkronisasi, implementasi yang menunggu ...).Metode asinkron berperilaku berbeda dari yang sinkron, karena saya yakin Anda sadar. Saat runtime, untuk mengonversi panggilan async menjadi panggilan sinkron adalah sepele, tetapi sebaliknya tidak bisa dikatakan. Jadi karena itu logikanya menjadi, mengapa kita tidak membuat metode async dari setiap metode yang mungkin memerlukannya dan membiarkan pemanggil "mengkonversi" yang diperlukan untuk metode sinkron?
Dalam arti itu seperti memiliki metode yang melempar pengecualian dan lainnya yang "aman" dan tidak akan melempar bahkan jika terjadi kesalahan. Pada titik mana pembuat kode berlebihan untuk menyediakan metode ini yang jika tidak dapat dikonversi satu sama lain?
Dalam hal ini ada dua aliran pemikiran: satu adalah untuk membuat beberapa metode, masing-masing memanggil metode lain yang mungkin pribadi memungkinkan untuk memberikan parameter opsional atau perubahan kecil pada perilaku seperti tidak sinkron. Yang lain adalah untuk meminimalkan metode antarmuka untuk telanjang penting menyerahkannya kepada pemanggil untuk melakukan modifikasi yang diperlukan sendiri.
Jika Anda dari sekolah pertama, ada logika tertentu untuk mendedikasikan kelas menuju panggilan sinkron dan asinkron untuk menghindari menggandakan setiap panggilan. Microsoft cenderung mendukung aliran pemikiran ini, dan dengan konvensi, untuk tetap konsisten dengan gaya yang disukai oleh Microsoft, Anda juga harus memiliki versi Async, dengan cara yang hampir sama dengan antarmuka yang hampir selalu dimulai dengan "I". Biarkan saya menekankan bahwa itu tidak salah , karena itu lebih baik untuk menjaga gaya yang konsisten dalam proyek daripada melakukannya dengan "cara yang benar" dan secara radikal mengubah gaya untuk pengembangan yang Anda tambahkan ke proyek.
Yang mengatakan, saya cenderung menyukai sekolah kedua, yaitu untuk meminimalkan metode antarmuka. Jika saya pikir suatu metode dapat dipanggil dengan cara asinkron, metode untuk saya adalah asinkron. Penelepon dapat memutuskan apakah akan menunggu sampai tugas itu selesai sebelum melanjutkan. Jika antarmuka ini adalah antarmuka ke pustaka, ada cara yang lebih masuk akal untuk melakukannya dengan cara ini untuk meminimalkan jumlah metode yang harus Anda hilangkan atau sesuaikan. Jika antarmuka untuk penggunaan internal dalam proyek saya, saya akan menambahkan metode untuk setiap panggilan yang diperlukan di seluruh proyek saya untuk parameter yang disediakan dan tidak ada metode "ekstra", dan bahkan kemudian, hanya jika perilaku metode ini belum tercakup. dengan metode yang ada.
Namun, seperti banyak hal di bidang ini, sebagian besar bersifat subjektif. Kedua pendekatan memiliki pro dan kontra. Microsoft juga memulai konvensi penambahan huruf indikatif jenis di awal nama variabel, dan "m_" untuk menunjukkan itu adalah anggota, yang mengarah ke nama variabel seperti
m_pUser
. Maksud saya adalah bahwa bahkan Microsoft tidak bisa salah, dan dapat membuat kesalahan juga.Yang mengatakan, jika proyek Anda mengikuti konvensi Async ini, saya akan menyarankan Anda untuk menghormatinya dan melanjutkan gaya. Dan hanya sekali Anda diberi proyek Anda sendiri, Anda dapat menulisnya dengan cara yang Anda inginkan.
sumber
.Wait()
metode dan suka dapat menyebabkan konsekuensi negatif, dan dalam js, sejauh yang saya tahu, itu tidak mungkin sama sekali.Promise.resolve(x)
dan kemudian menambahkan panggilan balik ke sana, panggilan balik itu tidak akan segera dieksekusi.Mari kita bayangkan ada cara untuk memungkinkan Anda memanggil fungsi secara async tanpa mengubah tandatangan mereka.
Itu akan sangat keren dan tidak ada yang akan merekomendasikan Anda mengubah nama mereka.
Tapi, fungsi asinkron yang sebenarnya, bukan hanya yang menunggu fungsi async lain, tetapi level terendah memiliki beberapa struktur untuk mereka yang spesifik dengan sifat async mereka. misalnya
Itu adalah dua bit informasi yang dibutuhkan oleh kode panggilan untuk menjalankan kode dengan cara async yang disukai
Task
danasync
diringkas.Ada lebih dari satu cara untuk melakukan fungsi asinkron,
async Task
hanya satu pola yang dibangun ke dalam kompiler melalui jenis kembali sehingga Anda tidak perlu secara manual menautkan acarasumber
Saya akan membahas poin utama dengan cara yang kurang C # ness dan lebih umum:
Saya akan mengatakan bahwa itu hanya tergantung pada pilihan yang Anda buat dalam desain API Anda dan apa yang Anda izinkan kepada pengguna.
Jika Anda ingin satu fungsi API Anda hanya async, ada sedikit minat mengikuti konvensi penamaan. Selalu saja mengembalikan Tugas <> / Janji <> / Masa Depan <> / ... sebagai jenis pengembalian, ini adalah dokumentasi sendiri. Jika ingin jawaban yang disinkronkan, ia masih dapat melakukan itu dengan menunggu, tetapi jika ia selalu melakukan itu, itu membuat sedikit pelat baja.
Namun, jika Anda hanya menyinkronkan API Anda, itu berarti bahwa jika pengguna menginginkannya menjadi async, ia harus mengelola sendiri bagian async itu.
Ini dapat membuat banyak pekerjaan ekstra, namun juga dapat memberikan lebih banyak kontrol kepada pengguna tentang berapa banyak panggilan serentak yang ia izinkan, tempatkan waktu habis, retrys, dan sebagainya.
Dalam sistem besar dengan API besar, menerapkan sebagian besar dari mereka untuk disinkronkan secara default mungkin lebih mudah dan lebih efisien daripada mengelola secara independen setiap bagian dari API Anda, khususnya jika mereka berbagi sumber daya (sistem file, CPU, database, ...).
Bahkan untuk bagian yang paling kompleks, Anda dapat memiliki dua implementasi yang sempurna dari bagian yang sama dari API Anda, satu sinkron melakukan hal-hal praktis, satu asinkron dengan mengandalkan sinkron yang menangani hal-hal dan hanya mengelola konkurensi, beban, waktu tunggu, dan coba lagi .
Mungkin orang lain bisa berbagi pengalamannya dengan itu karena saya kurang pengalaman dengan sistem seperti itu.
sumber