Saya bekerja dengan basis kode baru yang banyak menggunakan async / menunggu. Sebagian besar orang di tim saya juga cukup baru untuk async / menunggu. Kami umumnya cenderung berpegang pada Praktik Terbaik sebagaimana Ditentukan oleh Microsoft , tetapi umumnya membutuhkan konteks kami untuk mengalir melalui panggilan async dan bekerja dengan perpustakaan yang tidak ConfigureAwait(false)
.
Gabungkan semua hal itu dan kami mengalami kebuntuan async yang dijelaskan dalam artikel ... mingguan. Mereka tidak muncul selama pengujian unit, karena sumber data kami yang diejek (biasanya via Task.FromResult
) tidak cukup untuk memicu kebuntuan. Jadi selama uji runtime atau integrasi, beberapa panggilan layanan keluar untuk makan siang dan tidak pernah kembali. Itu membunuh server, dan umumnya membuat kekacauan.
Masalahnya adalah bahwa melacak di mana kesalahan itu dibuat (biasanya tidak async sepanjang jalan) umumnya melibatkan inspeksi kode manual, yang memakan waktu dan tidak mampu otomatis.
Apa cara yang lebih baik untuk mendiagnosis apa yang menyebabkan kebuntuan?
async
artikel orang ini ?Jawaban:
Oke - Saya tidak yakin apakah yang berikut ini akan membantu Anda, karena saya membuat beberapa asumsi dalam mengembangkan solusi yang mungkin atau mungkin tidak benar dalam kasus Anda. Mungkin "solusi" saya terlalu teoretis dan hanya berfungsi untuk contoh buatan - saya belum melakukan pengujian di luar hal-hal di bawah ini.
Selain itu, saya akan melihat solusi berikut lebih dari solusi nyata tetapi mengingat kurangnya tanggapan saya pikir itu mungkin masih lebih baik daripada tidak sama sekali (saya terus menonton pertanyaan Anda menunggu solusi, tetapi tidak melihat satu diposting saya mulai bermain sekitar dengan masalah ini).
Tetapi cukup mengatakan: Katakanlah kita memiliki layanan data sederhana yang dapat digunakan untuk mengambil integer:
Implementasi sederhana menggunakan kode asinkron:
Sekarang, muncul masalah, jika kita menggunakan kode "salah" seperti yang diilustrasikan oleh kelas ini.
Foo
salah mengaksesTask.Result
alih-alihawait
hasil sepertiBar
halnya:Yang kami (Anda) butuhkan sekarang adalah cara untuk menulis tes yang berhasil ketika memanggil
Bar
tetapi gagal saat memanggilFoo
(setidaknya jika saya mengerti pertanyaan dengan benar ;-)).Saya akan membiarkan kode berbicara; inilah yang saya hasilkan (menggunakan tes Visual Studio, tetapi harus bekerja menggunakan NUnit juga):
DataServiceMock
memanfaatkanTaskCompletionSource<T>
. Ini memungkinkan kita untuk mengatur hasil pada titik yang ditentukan dalam uji coba yang mengarah ke tes berikut. Perhatikan bahwa kami menggunakan delegasi untuk mengembalikan kembali TaskCompletionSource ke pengujian. Anda mungkin juga memasukkan ini ke dalam metode Inisialisasi pengujian dan gunakan properti.Apa yang terjadi di sini adalah bahwa kami terlebih dahulu memverifikasi bahwa kami dapat meninggalkan metode tanpa memblokir (ini tidak akan berfungsi jika seseorang mengakses
Task.Result
- dalam hal ini kami akan mengalami batas waktu karena hasil tugas tidak tersedia sampai setelah metode telah kembali ).Kemudian, kita mengatur hasilnya (sekarang metode dapat dijalankan) dan kami memverifikasi hasilnya (di dalam unit test kita dapat mengakses Task.Result karena kita benar - benar ingin pemblokiran terjadi).
Kelas tes lengkap -
BarTest
berhasil danFooTest
gagal seperti yang diinginkan.Dan kelas pembantu kecil untuk menguji kebuntuan / batas waktu:
sumber
Berikut adalah strategi yang saya gunakan dalam aplikasi besar dan sangat, sangat multithread:
Pertama, Anda memerlukan beberapa struktur data di sekitar mutex (sayangnya) dan tidak membuat direktori panggilan sinkronisasi. Dalam struktur data itu, ada tautan ke mutex yang sebelumnya dikunci. Setiap mutex memiliki "level" mulai dari 0, yang Anda tetapkan saat mutex dibuat dan tidak pernah dapat berubah.
Dan aturannya adalah: Jika sebuah mutex terkunci, Anda hanya boleh mengunci mutex lain di tingkat yang lebih rendah. Jika Anda mengikuti aturan itu, maka Anda tidak dapat memiliki deadlock. Ketika Anda menemukan pelanggaran, aplikasi Anda masih aktif dan berfungsi baik.
Ketika Anda menemukan pelanggaran, ada dua kemungkinan: Anda mungkin telah menetapkan level yang salah. Anda mengunci A diikuti dengan mengunci B, jadi B seharusnya memiliki level yang lebih rendah. Jadi, Anda memperbaiki level dan coba lagi.
Kemungkinan lainnya: Anda tidak dapat memperbaikinya. Beberapa kode Anda mengunci A diikuti dengan mengunci B, sementara beberapa kode lainnya mengunci B diikuti dengan mengunci A. Tidak ada cara untuk menetapkan level untuk mengizinkan ini. Dan tentu saja ini adalah kebuntuan potensial: Jika kedua kode berjalan secara bersamaan pada utas yang berbeda, ada kemungkinan kebuntuan.
Setelah memperkenalkan ini, ada fase yang agak pendek di mana level harus disesuaikan, diikuti oleh fase yang lebih panjang di mana deadlock potensial ditemukan.
sumber
Apakah Anda menggunakan Async / Menunggu sehingga Anda dapat memparalelkan panggilan mahal seperti ke basis data? Bergantung pada jalur eksekusi di DB ini mungkin tidak mungkin.
Cakupan uji coba dengan async / menunggu dapat menjadi tantangan dan tidak ada yang seperti penggunaan produksi nyata untuk menemukan bug. Satu pola yang dapat Anda pertimbangkan adalah meneruskan ID korelasi dan mencatatnya di tumpukan, kemudian memiliki batas waktu cascading yang mencatat kesalahan. Ini lebih merupakan pola SOA tetapi setidaknya itu akan memberi Anda rasa dari mana asalnya. Kami menggunakan ini dengan Splunk untuk menemukan kebuntuan.
sumber