Setelah pertanyaan ini, saya merasa nyaman saat menggunakan operasi async di ASP.NET MVC. Jadi, saya menulis dua posting blog tentang itu:
Saya memiliki terlalu banyak kesalahpahaman dalam pikiran saya tentang operasi asinkron di ASP.NET MVC.
Saya selalu mendengar kalimat ini: Aplikasi dapat meningkatkan skala jika operasi berjalan secara tidak sinkron
Dan saya sering mendengar kalimat semacam ini: jika Anda memiliki volume lalu lintas yang besar, Anda mungkin lebih baik tidak melakukan pertanyaan secara tidak sinkron - mengonsumsi 2 utas tambahan untuk melayani satu permintaan mengambil sumber daya dari permintaan masuk lainnya.
Saya pikir kedua kalimat itu tidak konsisten.
Saya tidak memiliki banyak informasi tentang cara kerja threadpool di ASP.NET tetapi saya tahu bahwa threadpool memiliki ukuran terbatas untuk utas. Jadi, kalimat kedua harus terkait dengan masalah ini.
Dan saya ingin tahu apakah operasi asinkron dalam ASP.NET MVC menggunakan utas dari ThreadPool di .NET 4?
Misalnya, ketika kita menerapkan AsyncController, bagaimana struktur aplikasi? Jika saya mendapatkan traffic yang besar, apakah itu ide yang baik untuk mengimplementasikan AsyncController?
Apakah ada orang di luar sana yang dapat mengambil tirai hitam ini di depan mata saya dan menjelaskan kepada saya kesepakatan tentang asinkron di ASP.NET MVC 3 (NET 4)?
Edit:
Saya telah membaca dokumen di bawah ini hampir ratusan kali dan saya mengerti masalah utamanya tetapi saya masih bingung karena terlalu banyak komentar yang tidak konsisten di luar sana.
Menggunakan Asynchronous Controller di ASP.NET MVC
Edit:
Mari kita asumsikan saya memiliki aksi controller seperti di bawah ini (bukan implementasi dari AsyncController
olah):
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
Seperti yang Anda lihat di sini, saya menjalankan operasi dan melupakannya. Kemudian, saya segera kembali tanpa menunggu itu selesai.
Dalam hal ini, apakah ini harus menggunakan utas dari threadpool? Jika demikian, setelah selesai, apa yang terjadi pada utas itu? Apakah GC
masuk dan membersihkan setelah selesai?
Edit:
Untuk jawaban @ Darwin, berikut adalah contoh kode async yang berbicara ke basis data:
public class FooController : AsyncController {
//EF 4.2 DbContext instance
MyContext _context = new MyContext();
public void IndexAsync() {
AsyncManager.OutstandingOperations.Increment(3);
Task<IEnumerable<Foo>>.Factory.StartNew(() => {
return
_context.Foos;
}).ContinueWith(t => {
AsyncManager.Parameters["foos"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<Bars>>.Factory.StartNew(() => {
return
_context.Bars;
}).ContinueWith(t => {
AsyncManager.Parameters["bars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<FooBar>>.Factory.StartNew(() => {
return
_context.FooBars;
}).ContinueWith(t => {
AsyncManager.Parameters["foobars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
}
public ViewResult IndexCompleted(
IEnumerable<Foo> foos,
IEnumerable<Bar> bars,
IEnumerable<FooBar> foobars) {
//Do the regular stuff and return
}
}
sumber
Jawaban:
Berikut ini adalah artikel yang sangat baik saya sarankan Anda membaca untuk lebih memahami pemrosesan asynchronous di ASP.NET (yang mewakili apa yang pada dasarnya mewakili pengontrol asinkron).
Pertama mari kita pertimbangkan tindakan sinkron standar:
Ketika permintaan dibuat untuk tindakan ini, sebuah thread diambil dari kumpulan thread dan tubuh dari tindakan ini dieksekusi pada thread ini. Jadi jika pemrosesan di dalam tindakan ini lambat, Anda memblokir utas ini untuk seluruh pemrosesan, jadi utas ini tidak dapat digunakan kembali untuk memproses permintaan lainnya. Di akhir eksekusi permintaan, utas dikembalikan ke utas kolam.
Sekarang mari kita ambil contoh pola asinkron:
Ketika permintaan dikirim ke tindakan Indeks, utas diambil dari kumpulan utas dan tubuh
IndexAsync
metode dieksekusi. Setelah badan metode ini selesai dijalankan, utas dikembalikan ke kumpulan utas. Kemudian, menggunakan standarAsyncManager.OutstandingOperations
, setelah Anda memberi sinyal penyelesaian operasi async, utas lain diambil dari kumpulan utas dan badanIndexCompleted
tindakan dieksekusi di atasnya dan hasilnya diberikan kepada klien.Jadi yang dapat kita lihat dalam pola ini adalah bahwa permintaan HTTP klien tunggal dapat dieksekusi oleh dua utas yang berbeda.
Sekarang bagian yang menarik terjadi di dalam
IndexAsync
metode. Jika Anda memiliki operasi pemblokiran di dalamnya, Anda benar-benar menyia-nyiakan seluruh tujuan pengontrol asinkron karena Anda memblokir utas pekerja (ingat bahwa tubuh tindakan ini dijalankan pada utas yang ditarik dari kumpulan utas).Jadi kapan kita dapat mengambil manfaat nyata dari pengontrol asinkron yang mungkin Anda tanyakan?
IMHO, kita dapat memperoleh sebagian besar ketika kita memiliki operasi intensif I / O (seperti basis data dan panggilan jaringan ke layanan jarak jauh). Jika Anda memiliki operasi intensif CPU, tindakan asinkron tidak akan membawa banyak manfaat bagi Anda.
Jadi mengapa kita dapat memperoleh manfaat dari operasi intensif I / O? Karena kita bisa menggunakan I / O Penyelesaian Ports . IOCP sangat kuat karena Anda tidak menggunakan utas atau sumber daya apa pun di server selama pelaksanaan seluruh operasi.
Bagaimana mereka bekerja?
Misalkan kita ingin mengunduh konten halaman web jarak jauh menggunakan metode WebClient.DownloadStringAsync . Anda memanggil metode ini yang akan mendaftarkan IOCP dalam sistem operasi dan segera kembali. Selama pemrosesan seluruh permintaan, tidak ada utas yang digunakan di server Anda. Semuanya terjadi di server jarak jauh. Ini bisa memakan banyak waktu tetapi Anda tidak peduli karena Anda tidak membahayakan utas pekerja Anda. Setelah respons diterima, IOCP ditandai, utas diambil dari utas dan panggilan balik dieksekusi pada utas ini. Tapi seperti yang Anda lihat, selama seluruh proses, kami belum memonopoli utas apa pun.
Hal yang sama berlaku dengan metode seperti FileStream.BeginRead, SqlCommand.BeginExecute, ...
Bagaimana dengan memparalelkan beberapa panggilan basis data? Misalkan Anda memiliki tindakan pengontrol sinkron di mana Anda melakukan 4 pemblokiran panggilan basis data secara berurutan. Sangat mudah untuk menghitung bahwa jika setiap panggilan basis data membutuhkan 200 ms, tindakan pengontrol Anda akan membutuhkan sekitar 800 ms untuk dijalankan.
Jika Anda tidak perlu menjalankan panggilan itu secara berurutan, akankah memparalelasinya meningkatkan kinerja?
Itu pertanyaan besar, yang tidak mudah dijawab. Mungkin ya mungkin tidak. Ini sepenuhnya akan tergantung pada bagaimana Anda menerapkan panggilan database tersebut. Jika Anda menggunakan pengontrol async dan Port Penyelesaian I / O seperti yang dibahas sebelumnya, Anda akan meningkatkan kinerja tindakan pengontrol ini dan tindakan lainnya juga, karena Anda tidak akan memonopoli thread pekerja.
Di sisi lain, jika Anda menerapkannya dengan buruk (dengan panggilan basis data pemblokiran dilakukan pada utas dari utas), Anda pada dasarnya akan menurunkan total waktu pelaksanaan tindakan ini menjadi sekitar 200 ms, tetapi Anda akan menggunakan 4 utas pekerja sehingga Anda mungkin telah menurunkan kinerja permintaan lain yang mungkin menjadi kelaparan karena tidak ada utas untuk memprosesnya.
Jadi itu sangat sulit dan jika Anda tidak merasa siap untuk melakukan tes ekstensif pada aplikasi Anda, jangan menerapkan pengendali tidak sinkron, karena kemungkinan Anda akan melakukan lebih banyak kerusakan daripada manfaat. Terapkan hanya jika Anda memiliki alasan untuk melakukannya: misalnya Anda telah mengidentifikasi bahwa tindakan pengontrol sinkron standar adalah penghambat aplikasi Anda (setelah melakukan tes beban yang luas dan tentu saja pengukuran).
Sekarang mari kita perhatikan contoh Anda:
Ketika permintaan diterima untuk tindakan Indeks, sebuah thread diambil dari kumpulan thread untuk mengeksekusi tubuhnya, tetapi tubuhnya hanya menjadwalkan tugas baru menggunakan TPL . Jadi eksekusi tindakan berakhir dan utas dikembalikan ke utas kumpulan. Kecuali itu, TPL menggunakan utas dari kumpulan utas untuk melakukan pemrosesan. Jadi, bahkan jika utas asli dikembalikan ke kumpulan utas, Anda telah menarik utas lain dari kumpulan ini untuk menjalankan isi tugas. Jadi Anda telah membahayakan 2 utas dari kolam berharga Anda.
Sekarang mari kita pertimbangkan hal berikut:
Dalam hal ini kami secara manual memunculkan sebuah utas. Dalam hal ini, eksekusi tubuh tindakan Indeks mungkin memakan waktu sedikit lebih lama (karena memunculkan thread baru lebih mahal daripada menarik satu dari kumpulan yang ada). Tetapi pelaksanaan operasi logging lanjutan akan dilakukan pada utas yang bukan bagian dari kumpulan. Jadi kami tidak membahayakan utas dari kolam yang tetap gratis untuk melayani permintaan lain.
sumber
System.Threading.Task
) yang menjalankanIndexAsync
metode inside . Di dalam operasi itu, kami membuat panggilan db ke server. Jadi, semuanya adalah operasi intensif I / O, kan? Dalam hal itu, apakah kita membuat 4 utas terpisah (atau mendapat 4 utas terpisah dari kumpulan-utas)? Dengan asumsi saya memiliki mesin multi-core mereka akan berjalan secara paralel juga, bukan?SqlCommand.ExecuteReader
Anda membuang-buang semuanya karena ini adalah panggilan pemblokiran. Anda memblokir utas yang digunakan panggilan ini dan jika utas ini merupakan utas dari kumpulan itu sangat buruk. Anda akan mendapatkan keuntungan hanya jika Anda menggunakan I / O Penyelesaian Ports:SqlCommand.BeginExecuteReader
. Jika Anda tidak menggunakan IOCP apa pun yang Anda lakukan, jangan gunakan pengontrol async karena Anda akan melakukan lebih banyak kerusakan daripada manfaat pada kinerja keseluruhan aplikasi Anda._context.Foo
Anda sebenarnya tidak mengeksekusi apa pun. Anda hanya membangun pohon ekspresi. Berhati-hatilah dengan ini. Eksekusi kueri ditangguhkan hanya ketika Anda mulai menghitung lebih dari hasil. Dan jika ini terjadi dalam tampilan ini mungkin menjadi bencana bagi kinerja. Untuk dengan bersemangat mengeksekusi menambahkan kueri EF.ToList()
di akhir.Ya - semua utas berasal dari kumpulan utas. Aplikasi MVC Anda sudah multi-utas, saat permintaan masuk utas baru akan diambil dari kumpulan dan digunakan untuk melayani permintaan. Utas itu akan 'dikunci' (dari permintaan lain) sampai permintaan sepenuhnya dilayani dan diselesaikan. Jika tidak ada utas yang tersedia di kolam permintaan akan harus menunggu sampai tersedia.
Jika Anda memiliki pengontrol async, mereka masih mendapatkan utas dari kolam tetapi saat melayani permintaan mereka dapat menyerahkan utas, sambil menunggu sesuatu terjadi (dan utas itu dapat diberikan kepada permintaan lain) dan ketika permintaan awal membutuhkan utas lagi mendapat satu dari kolam renang.
Perbedaannya adalah bahwa jika Anda memiliki banyak permintaan jangka panjang (di mana utas menunggu tanggapan dari sesuatu), Anda mungkin kehabisan utas dari kolam untuk melayani bahkan permintaan dasar. Jika Anda memiliki pengontrol async, Anda tidak memiliki utas lagi tetapi utas yang menunggu dikembalikan ke kumpulan dan dapat melayani permintaan lainnya.
Sebuah contoh kehidupan yang hampir nyata ... Pikirkan itu seperti naik bus, ada lima orang menunggu untuk naik, yang pertama naik, membayar dan duduk (pengemudi dilayani permintaan mereka), Anda naik (pengemudi sedang melayani permintaan Anda) tetapi Anda tidak dapat menemukan uang Anda; ketika Anda meraba-raba di saku Anda, pengemudi menyerah pada Anda dan mendapatkan dua orang berikutnya (melayani permintaan mereka), ketika Anda menemukan uang Anda pengemudi mulai berurusan dengan Anda lagi (menyelesaikan permintaan Anda) - orang kelima harus menunggu sampai Anda sudah selesai tetapi orang ketiga dan keempat dilayani sementara Anda setengah jalan dilayani. Ini berarti bahwa pengemudi adalah satu-satunya utas dari kolam dan penumpang adalah permintaan. Terlalu rumit untuk menulis bagaimana cara kerjanya jika ada dua driver tetapi Anda dapat membayangkan ...
Tanpa pengontrol async, penumpang di belakang Anda harus menunggu lama saat Anda mencari uang Anda, sementara itu sopir bus tidak akan bekerja.
Jadi kesimpulannya adalah, jika banyak orang tidak tahu di mana uang mereka berada (yaitu memerlukan waktu lama untuk menanggapi sesuatu yang diminta sopir) pengontrol async dapat membantu throughput permintaan, mempercepat proses dari beberapa orang. Tanpa pengontrol aysnc, semua orang menunggu sampai orang di depan benar-benar ditangani. TAPI jangan lupa bahwa di MVC Anda memiliki banyak supir bis dalam satu bus sehingga async bukanlah pilihan otomatis.
sumber
Ada dua konsep yang dimainkan di sini. Pertama-tama kita dapat membuat kode kita berjalan secara paralel untuk mengeksekusi lebih cepat atau menjadwalkan kode pada utas lain untuk menghindari membuat pengguna menunggu. Contoh yang Anda miliki
termasuk dalam kategori kedua. Pengguna akan mendapatkan respons yang lebih cepat tetapi beban kerja total pada server lebih tinggi karena harus melakukan pekerjaan yang sama + menangani threading.
Contoh lain dari ini adalah:
Karena permintaan berjalan secara paralel, pengguna tidak perlu menunggu selama jika dilakukan secara serial. Tetapi Anda harus menyadari bahwa kami menggunakan lebih banyak sumber daya di sini daripada jika kami menjalankan serial karena kami menjalankan kode di banyak utas sementara kami juga menunggu di atas utas.
Ini sangat baik dalam skenario klien. Dan sangat umum di sana untuk membungkus kode lama berjalan sinkron dalam tugas baru (jalankan di utas lain) juga menjaga ui responsif atau parallize untuk membuatnya lebih cepat. Sebuah utas masih digunakan untuk seluruh durasi. Pada server dengan beban tinggi ini bisa menjadi bumerang karena Anda benar-benar menggunakan lebih banyak sumber daya. Inilah yang diperingatkan orang tentang Anda
Pengontrol Async di MVC memiliki tujuan lain. Intinya di sini adalah untuk menghindari memiliki sittings benang di sekitar melakukan apa-apa (yang dapat merusak skalabilitas). Ini benar-benar hanya masalah jika API yang Anda panggil memiliki metode async. Seperti WebClient.DowloadStringAsync ().
Intinya adalah Anda dapat membiarkan utas Anda dikembalikan untuk menangani permintaan baru sampai permintaan web selesai di mana ia akan memanggil Anda callback yang mendapat utas yang sama atau baru dan menyelesaikan permintaan.
Saya harap Anda memahami perbedaan antara asinkron dan paralel. Pikirkan kode paralel sebagai kode tempat utas Anda berada dan tunggu hasilnya. Sementara kode asinkron adalah kode tempat Anda akan diberi tahu ketika kode selesai dan Anda dapat kembali bekerja, sementara itu utas dapat melakukan pekerjaan lain.
sumber
Aplikasi dapat meningkatkan skala jika operasi berjalan secara tidak sinkron, tetapi hanya jika ada sumber daya yang tersedia untuk melayani operasi tambahan .
Operasi asinkron memastikan bahwa Anda tidak pernah memblokir tindakan karena tindakan yang ada sedang berlangsung. ASP.NET memiliki model asinkron yang memungkinkan banyak permintaan untuk dieksekusi berdampingan. Mungkin saja untuk mengantri permintaan dan memprosesnya FIFO, tetapi ini tidak akan skala dengan baik ketika Anda memiliki ratusan permintaan yang antri dan setiap permintaan membutuhkan 100 ms untuk diproses.
Jika Anda memiliki volume lalu lintas yang sangat besar, Anda mungkin lebih baik tidak melakukan permintaan secara serempak, karena mungkin tidak ada sumber daya tambahan untuk melayani permintaan . Jika tidak ada sumber daya cadangan, permintaan Anda dipaksa untuk mengantri, memakan waktu lebih lama secara eksponensial atau gagal total, dalam hal ini overhead tidak sinkron (operasi mutex dan pengalihan konteks) tidak memberi Anda apa pun.
Sejauh ASP.NET berjalan, Anda tidak punya pilihan - itu menggunakan model asinkron, karena itulah yang masuk akal untuk model server-klien. Jika Anda harus menulis kode Anda sendiri secara internal yang menggunakan pola async untuk mencoba meningkatkan skala, kecuali Anda mencoba mengelola sumber daya yang dibagikan di antara semua permintaan, Anda tidak akan benar-benar melihat peningkatan karena sudah dibungkus dalam proses asinkron yang tidak memblokir hal lain.
Pada akhirnya, itu semua subjektif sampai Anda benar-benar melihat apa yang menyebabkan kemacetan dalam sistem Anda. Terkadang jelas bahwa pola asinkron akan membantu (dengan mencegah pemblokiran sumber daya yang antri). Pada akhirnya hanya mengukur dan menganalisis suatu sistem yang dapat menunjukkan di mana Anda dapat memperoleh efisiensi.
Edit:
Dalam contoh Anda, the
Task.Factory.StartNew
panggilan akan mengantri operasi di .NET thread-pool. Sifat utas Thread Pool harus digunakan kembali (untuk menghindari biaya membuat / menghancurkan banyak utas). Setelah operasi selesai, utas dilepaskan kembali ke kolam untuk digunakan kembali oleh permintaan lain (Pengumpul Sampah tidak benar-benar terlibat kecuali Anda membuat beberapa objek dalam operasi Anda, dalam hal ini mereka dikumpulkan sesuai normal pelingkupan).Sejauh menyangkut ASP.NET, tidak ada operasi khusus di sini. Permintaan ASP.NET selesai tanpa menghormati tugas asinkron. Satu-satunya kekhawatiran adalah jika kumpulan utas Anda jenuh (yaitu tidak ada utas yang tersedia untuk melayani permintaan saat ini dan pengaturan kumpulan tidak memungkinkan lebih banyak utas dibuat), dalam hal ini permintaan diblokir menunggu untuk memulai tugas sampai utas pool menjadi tersedia.
sumber
Task.Factory.StartNew
panggilan akan mengantri operasi di kolam thread .NET. . Dalam konteks ini, yang mana yang benar di sini: 1-) Ini membuat utas baru dan setelah selesai, utas itu kembali ke utas dan menunggu di sana untuk digunakan kembali. 2-) Ia mendapatkan utas dari threadpool dan utas itu kembali ke threadpool dan menunggu di sana untuk digunakan kembali. 3-) Dibutuhkan pendekatan yang paling efisien dan dapat melakukan keduanya.Ya, mereka menggunakan utas dari kumpulan utas. Sebenarnya ada panduan yang sangat bagus dari MSDN yang akan menangani semua pertanyaan Anda dan banyak lagi. Saya telah menemukan itu sangat berguna di masa lalu. Saksikan berikut ini!
http://msdn.microsoft.com/en-us/library/ee728598.aspx
Sementara itu, komentar + saran yang Anda dengar tentang kode asinkron harus diambil dengan sebutir garam. Sebagai permulaan, hanya membuat sesuatu yang async tidak selalu menjadikannya skala yang lebih baik, dan dalam beberapa kasus dapat membuat skala aplikasi Anda lebih buruk. Komentar lain yang Anda poskan tentang "volume lalu lintas yang sangat besar ..." juga hanya benar dalam konteks tertentu. Itu benar-benar tergantung pada apa operasi Anda lakukan, dan bagaimana mereka berinteraksi dengan bagian lain dari sistem.
Singkatnya, banyak orang memiliki banyak pendapat tentang async, tetapi mereka mungkin tidak benar di luar konteks. Saya akan mengatakan fokus pada masalah Anda yang sebenarnya, dan lakukan pengujian kinerja dasar untuk melihat apa yang dikontrol async, dll. Yang sebenarnya menangani aplikasi Anda .
sumber
GC
datang dan membersihkannya setelah saya selesai sehingga aplikasi saya tidak akan memiliki kebocoran memori, begitu seterusnya. Ada ide di bagian itu.Hal pertama yang bukan MVC tetapi IIS yang memelihara thread pool. Jadi setiap permintaan yang datang ke aplikasi MVC atau ASP.NET dilayani dari utas yang dikelola dalam kumpulan utas. Hanya dengan membuat aplikasi Asynch ia menjalankan tindakan ini di utas yang berbeda dan segera melepaskan utas sehingga permintaan lain dapat diambil.
Saya telah menjelaskan hal yang sama dengan video detail ( http://www.youtube.com/watch?v=wvg13n5V0V0/ "MVC Asynch controllers dan starvation thread") yang menunjukkan bagaimana kelaparan thread terjadi di MVC dan bagaimana meminimalkannya dengan menggunakan MVC Asynch controllers. Saya juga telah mengukur antrian permintaan menggunakan perfmon sehingga Anda dapat melihat bagaimana antrian permintaan diturunkan untuk MVC asynch dan bagaimana terburuknya untuk operasi Synch.
sumber