Asli:Login hyperlink terlihat.
Ringkasan: Bahkan jika Anda merasa tahu apa yang Anda lakukan, tidak aman untuk menyimpan apa pun di anggota ThreadStatic, CallContext, atau penyimpanan Thread Local dalam aplikasi ASP.Net jika nilainya mungkin telah ditetapkan sebelumnya. ke Page_Load (misalnya di IHttpModule atau konstruktor halaman), tetapi selama atau setelah akses.
[Pembaruan: Agustus 2008 Mengingat bahwa cukup banyak orang terus menautkan ke artikel ini, saya merasa perlu untuk mengklarifikasi bahwa perilaku pertukaran utas ini terjadi pada titik yang sangat spesifik dalam siklus hidup halaman, dan bukan ketika dirasakan.] Kata-kata saya setelah mengutip Jeff Newsom sangat disayangkan. Selain itu, saya sangat senang (dan tersanjung) melihat referensi ke artikel ini beberapa kali dalam diskusi desain tentang penanganan HttpContext yang tepat. Saya senang orang-orang menganggapnya berguna. ]
Ada banyak kebingungan tentang cara mengimplementasikan singleton khusus pengguna di ASP.Net - yaitu, data global bersifat global hanya untuk satu pengguna atau permintaan. Ini bukan persyaratan yang tidak biasa: publikasikan data transaksional, konteks keamanan, atau data "global" lainnya di satu tempat, daripada mendorongnya ke setiap panggilan metode, karena data loam memungkinkan implementasi yang lebih jelas (dan lebih mudah dibaca). Namun, jika Anda tidak berhati-hati, ini adalah tempat yang bagus untuk menembak diri sendiri di kaki (atau kepala). Saya pikir saya tahu apa yang sedang terjadi, tetapi saya tidak.
Opsi yang lebih disukai adalah singletonDisimpan di HttpContext.Current.Items, sederhana dan aman, tetapi menghubungkan singleton yang dimaksud dengan penggunaannya dalam aplikasi ASP.Net. Jika singleton gagal dalam objek bisnis Anda, maka ini tidak ideal. Bahkan jika Anda membungkus akses properti dalam pernyataan if
Summary: Bahkan jika Anda merasa tahu apa yang Anda lakukan, tidak aman untuk menyimpan apa pun di anggota ThreadStatic, CallContext, atau Penyimpanan Lokal Thread dalam aplikasi ASP.Net, jika ada kemungkinan bahwa nilai mungkin disiapkan sebelum Page_Load (misalnya di IHttpModule, atau konstruktor halaman) tetapi diakses selama atau sesudahnya.
[Pembaruan: Agustus 2008 Mengingat jumlah orang yang cukup besar yang terus menautkan ke posting ini, saya merasa perlu untuk mengklarifikasi bahwa perilaku pertukaran utas ini terjadi pada titik yang sangat spesifik di halaman siklus hidup dan bukan kapan pun terasa seperti itu. Kata-kata saya setelah kutipan Jef Newson sangat disayangkan. Selain itu, saya sangat bersyukur (dan tersanjung) dengan berapa kali saya melihat posting ini dikutip dalam diskusi desain seputar menangani HttpContext dengan tepat. Saya senang orang-orang menganggapnya berguna.]
Ada banyak kebingungan tentang cara mengimplementasikan singleton khusus pengguna di ASP.Net - artinya data global yang hanya global untuk satu pengguna atau permintaan. Ini bukan persyaratan yang tidak biasa: menerbitkan Transaksi, konteks keamanan, atau data 'global' lainnya di satu tempat, daripada mendorongnya melalui setiap panggilan metode karena data tramp dapat membuat implementasi yang lebih bersih (dan lebih mudah dibaca). Namun, ini adalah tempat yang bagus untuk menembak diri sendiri di kaki (atau kepala) jika Anda tidak berhati-hati. Saya pikir saya tahu apa yang sedang terjadi, tetapi saya tidak.
Opsi yang disukai, menyimpan singleton Anda di HttpContext.Current.Items, sederhana dan aman, tetapi mengikat singleton yang dimaksud untuk digunakan dalam aplikasi ASP.Net. Jika singleton down dalam objek bisnis Anda, ini tidak ideal. Bahkan jika Anda membungkus property-access dalam pernyataan if
... maka Anda masih harus mereferensikan System.Web dari rakitan itu, yang cenderung memasukkan lebih banyak objek 'webby' di tempat yang salah.
Alternatifnya adalah menggunakan anggota statis [ThreadStatic], penyimpanan lokal Thread (yang hampir sama jumlahnya), atau CallContext.
Masalah dengan [ThreadStatic] didokumentasikan dengan baik, tetapi untuk meringkas: Initalizer lapangan hanya menembak pada utas pertama Data ThreadStatic perlu dibersihkan secara eksplisit (misalnya di EndRequest), karena meskipun Thread dapat dijangkau, data ThreadStatic tidak akan GC sehingga Anda mungkin membocorkan sumber daya. Data ThreadStatic hanya bagus dalam permintaan, karena permintaan berikutnya mungkin masuk ke thread yang berbeda, dan mendapatkan data orang lain. Scott Hanselman melakukannya dengan benar, bahwa ThreadStatic tidak bermain dengan baik dengan ASP.Net, tetapi tidak sepenuhnya menjelaskan alasannya.
Penyimpanan di CallContext mengurangi beberapa masalah ini, karena konteks mati di akhir permintaan dan GC akan terjadi pada akhirnya (meskipun Anda masih dapat membocorkan sumber daya hingga GC terjadi jika Anda menyimpan sekali pakai). Selain itu, CallContext adalah cara HttpContext disimpan, jadi harus baik-baik saja, bukan?. Terlepas dari itu, Anda akan berpikir (seperti yang saya lakukan) bahwa asalkan Anda membersihkan diri sendiri di akhir setiap permintaan, semuanya akan baik-baik saja: "Jika Anda menginisialisasi variabel ThreadStatic di awal permintaan dan menangani objek yang direferensikan dengan benar di akhir permintaan, saya akan mengambil risiko mengklaim bahwa tidak ada hal buruk yang akan terjadi
"Sekarang,Saya bisa salah. CLR mungkin berhenti menghosting utas di tengah jalan, membuat serialisasi tumpukannya di suatu tempat, memberinya tumpukan baru, dan membiarkannya mulai mengeksekusi。 Saya sangat meragukan ini. Saya kira bisa dibayangkan bahwa hyperthreading juga akan membuat segalanya sulit, tetapi saya juga meragukannya. ”
Jeff Newsom
"Jika Anda menginisialisasi variabel ThreadStatic di awal permintaan, dan Anda membuang objek yang direferensikan dengan benar di akhir permintaan, saya akan keluar dan mengklaim bahwa tidak ada buruk akan terjadi. Anda bahkan keren di antara konteks di AppDomain yang sama
"Sekarang, saya bisa salah dalam hal ini. clr berpotensi menghentikan thread terkelola di tengah aliran, menserialkan tumpukannya di suatu tempat, memberinya tumpukan baru, dan membiarkannya mulai dieksekusi. Saya sangat meragukannya. Saya kira bisa dibayangkan bahwa hyperthreading juga membuat segalanya menjadi sulit, tetapi saya juga meragukannya." Jef Newsom Pembaruan: Ini menyesatkan. Saya akan menjelaskan lebih lanjut nanti bahwa pertukaran utas ini hanya dapat terjadi antara BeginRequest dan Page_Load, tetapi referensi Jeff, menciptakan gambar yang sangat kuat yang gagal saya perbaiki segera.
Update: This was the misleading bit. I do explain further later on that this thread-swap can only happen between the BeginRequest and the Page_Load, but Jef's quote creates a very powerful image I failed to immediately correct. My bad. Jadi, pada titik tertentu, ASP.NET memutuskan bahwa ada terlalu banyak utas I/O yang memproses permintaan lain. […] Itu hanya menerima permintaan dan mengantrenya dalam objek antrean internal dalam runtime ASP.NET. Kemudian, setelah mengantri, utas I/O akan meminta utas pekerja, dan kemudian utas I/O akan kembali ke kumpulannya. […] Oleh karena itu, ASP.NET akan membiarkan pekerja tersebut menangani permintaan. Ini akan membawanya ke runtime ASP.NET, seperti halnya utas I/O pada beban rendah.
Jadi pada titik tertentu ASP.NET memutuskan bahwa ada terlalu banyak utas I/O yang memproses permintaan lain. [...] Itu hanya mengambil permintaan dan mengantrinya di objek antrean internal ini dalam runtime ASP.NET. Kemudian, setelah itu diantri, utas I/O akan meminta utas pekerja, dan kemudian utas I/O akan dikembalikan ke kumpulannya. [...] Jadi ASP.NET akan meminta utas pekerja itu memproses permintaan. Ini akan membawanya ke runtime ASP.NET, sama seperti utas I/O di bawah beban rendah. Sekarang saya selalu tahu tentang itu, tetapi saya pikir itu terjadi sangat awal dan saya tidak peduli. Namun, sepertinya saya salah. Kami mengalami masalah di aplikasi ASP.Net di mana pengguna mengklik tautan setelah mengklik tautan lain, dan aplikasi kami memiliki pengecualian referensi null di salah satu singleton (saya menggunakan CallContext alih-alih ThreadStatic untuk singleton, tetapi ternyata tidak relevan).
Saya melakukan beberapa penelitian tentang bagaimana tepatnya utas ASP.Net bekerja, dan mendapatkan pendapat yang saling bertentangan yang disamarkan sebagai fakta (permintaan adalah utas yang gesit dalam permintaan, sementara permintaan berlabuh pada utas selama masa pakainya), jadi saya menyalin masalah saya di aplikasi pengujian dengan halaman lambat (tidur sebentar) dan halaman cepat. Saya mengklik tautan ke halaman lambat, dan sebelum halaman kembali, saya mengklik tautan ke halaman cepat. Hasilnya (log4net dump dari apa yang terjadi) membuat saya terpesona.
Output menunjukkan bahwa untuk permintaan kedua, peristiwa BeginRequest dan konstruktor halaman di alur HttpModule diaktifkan pada satu utas, tetapi page_Load di thread lain. Utas kedua telah memigrasikan HttpContext dari utas pertama, tetapi bukan CallContext atau ThreadStatic (catatan: karena HttpContext itu sendiri disimpan di CallContext, ini berarti bahwa ASP.Net secara eksplisit memigrasikan HttpContext). Mari kita katakan lagi:
Sekarang saya selalu tahu tentang ini, tetapi saya berasumsi itu terjadi cukup awal dalam proses sehingga saya tidak peduli. Namun tampaknya saya salah. Kami mengalami masalah di aplikasi ASP.Net kami di mana pengguna mengklik satu tautan tepat setelah mengklik tautan lain, dan aplikasi kami meledak dengan pengecualian referensi null untuk salah satu singleton kami (saya menggunakan CallContext bukan ThreadStatic untuk singleton, tetapi ternyata itu tidak masalah).
Saya melakukan sedikit penelitian tentang bagaimana tepatnya ASP. Utas Net berfungsi, dan mendapat pendapat yang bertentangan dengan penyamaran sebagai fakta (permintaan adalah utas yang gesit dalam permintaan vs permintaan disematkan ke utas seumur hidupnya) jadi saya mereplikasi masalah dalam aplikasi uji dengan halaman lambat (tidur sedetik) dan halaman cepat. Saya mengklik tautan untuk halaman lambat dan sebelum halaman kembali saya mengklik tautan untuk halaman cepat. Hasilnya (dump log4net dari apa yang sedang terjadi) mengejutkan saya.
Apa yang ditunjukkan output adalah bahwa - untuk permintaan kedua - peristiwa BeginRequest di alur HttpModule dan konstruktor halaman diaktifkan pada satu utas, tetapi Page_Load diaktifkan pada yang lain. Utas kedua memiliki HttpContext yang dimigrasikan dari yang pertama, tetapi bukan CallContext atau ThreadStatic (NB: karena HttpContext itu sendiri disimpan di CallContext, ini berarti ASP.Net adalah secara eksplisit memigrasikan HttpContext ke seberang). Mari kita jelaskan ini lagi:
- Pengalihan utas terjadi setelah IHttpHandler dibuat
- Setelah penginisialisasi bidang halaman dan konstruktor dijalankan
- Setelah peristiwa jenis BeginRequest, AuthenticateRequest, AquireSessionState yang digunakan oleh Global.ASA/IHttpModules.
- Hanya HttpContext yang dimigrasikan ke utas baru
Sakelar utas terjadi setelah IHttpHandler dibuat Setelah penginisialisasi bidang halaman dan konstruktor dijalankan Setelah peristiwa jenis BeginRequest, AuthenticateRequest, AquireSessionState yang digunakan Global.ASA / IHttpModules Anda. Hanya HttpContext yang bermigrasi ke utas baru Ini adalah PITA utama karena dari apa yang saya lihat, itu berarti bahwa satu-satunya opsi persistensi untuk perilaku kelas "ThreadStatic" di ASP.Net adalah menggunakan HttpContext. Jadi untuk objek bisnis Anda, Anda terus menggunakan if(HttpContext.Current!). =null) dan referensi System.Web (yuck), atau Anda harus membuat semacam model penyedia untuk persistensi statis, yang perlu diatur sebelum mengakses singleton ini. Mual ganda.
Tolong katakan bahwa ini tidak terjadi.
Lampiran: Log lengkap:
Ini adalah PITA utama, karena sejauh yang saya lihat berarti satu-satunya opsi persistensi untuk perilaku 'ThreadStatic' dalam ASP.Net adalah menggunakan HttpContext. Jadi untuk objek bisnis Anda, Anda terjebak dengan if(HttpContext.Current!=null) dan referensi System.Web (yuck) atau Anda harus membuat semacam model penyedia untuk Anda persistensi statis, yang perlu disiapkan sebelum titik di mana salah satu singleton ini diakses. Yuck ganda.
Tolong seseorang mengatakan itu tidak begitu.
Appendix: That log in full: [3748] INFORMASI 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/SlowPage.aspx [3748] INFORMASI 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(sekarang)=97, calldata= [3748] INFO 11:10:05,249 ASP. SlowPage_aspx.. ctor() - threadid=3748, threadhash=(cctor)97, threadhash(sekarang)=97, calldata=3748, logicalcalldata=3748 [3748] INFO 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(sekarang)=97, calldata=3748, logicalcalldata=3748 [3748] INFO 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - Halaman tidur lambat....
[2720] INFO 11:10:05,669 ASP. Global_asax. Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/FastPage.aspx [2720] INFO 11:10:05,679 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(sekarang)=1835, calldata= [2720] INFO 11:10:05,679 ASP. FastPage_aspx.. ctor() - threadid=2720, threadhash=(cctor)1835, threadhash(sekarang)=1835, calldata=2720, logicalcalldata=2720, threadstatic=2720
[3748] INFO 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - Halaman lambat bangun.... [3748] INFO 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(sekarang)=97, calldata=3748, logicalcalldata=3748 [3748] INFO 11:10:06,350 ASP. Global_asax. Application_EndRequest() - threadid=3748, threadhash=97, threadhash(sekarang)=97, calldata=3748 [3748] INFO 11:10:06,350 ASP. Global_asax. Application_EndRequest() - END /ConcurrentRequestsDemo/SlowPage.aspx
[4748] INFO 11:10:06,791 ASP. FastPage_aspx. Page_Load() - threadid=2720, threadhash=(cctor)1835, threadhash(now)=1703, calldata=, logicalcalldata=, threadstatic= [4748] INFO 11:10:06,791 ASP. Global_asax. Application_EndRequest() - threadid=2720, threadhash=1835, threadhash(sekarang)=1703, calldata= [4748] INFO 11:10:06,791 ASP. Global_asax. Application_EndRequest() - END /ConcurrentRequestsDemo/FastPage.aspx Kuncinya adalah apa yang terjadi ketika Page_Load FastPage dipicu. ThreadID adalah 4748, tetapi ThreadID yang saya simpan di HttpContext ctor adalah 2720. Kode hash untuk utas logis adalah 1703, tetapi kode hash yang saya simpan di ctor adalah 1835. Semua data yang saya simpan di CallContext hilang (bahkan data yang ditandai ILogicalThreadAffinnative), tetapi HttpContext masih ada. Seperti yang Anda duga, ThreadStatic saya juga hilang.
Bagian kuncinya adalah apa yang terjadi ketika Page_Load FastPage diaktifkan. ThreadID adalah 4748, tetapi threadID yang saya simpan di HttpContext di ctor adalah 2720. Kode hash untuk utas logis adalah 1703, tetapi yang saya simpan di ctor adalah 1835. Semua data yang saya simpan di CallContext hilang (bahkan yang ditandai ILogicalThreadAffinative), tetapi HttpContext masih ada. Seperti yang Anda duga, ThreadStatic saya juga hilang. (Akhir)
|