|
|
2022-8-7 tarihinde yayınlandı 21:11:58
|
|
|
|

1: Arka plan
1. Hikaye anlat
Birkaç gün önce, .NET Gelişmiş Hata Gönderme makalesini github'a indekslediğimde ilginç bir yorum buldum, detaylar için makaleye bak, ekran görüntüsü şöyle:
Muhtemelen Winform'un ana başlığı altında Task.Result'u çalıştırmak çıkmaza yol açacak anlamına geliyor, ayrıca resimdeki referans bağlantısına da baktım, Stephen tam bir patron, ama bu makale esas olarak büyük bir metin paragrafının beyin yıkaması üzerine ve gördüğünüz şeyi gerçekten görmenize izin vermiyor, bu yüzden bunu windbg bakış açısından analiz edeceğim.
2: Windbg analizi
1. Gerçekten çıkmaz mı olacak?
Tabii ki, winform'u yıllardır oynamadım ve konsolda oynayıp oynamayacağını bilemiyorum.
Kod çok basit, programı çalıştır, tık tık ve gerçekten de arayüz takılı kalıyor, bu da biraz inanılmaz.
2. Çıkmazın nedenini arayın
Sonra, acele edin ve sürece windbg takın, öğrenmek için.
1) Ana konuya bak Arayüz yanıt vermiyor, bu yüzden ana konu doğal olarak takılı kalıyor, bu yüzden ana konu şu anda ne yapıyor bakmanız gerekiyor. ~0s + !clrstack komutunu kullanın.
0:000> !clrstack OS Thread ID: 0x5a10 (0) Çocuk SP IP Çağrı Sitesi 0000004d10dfde00 00007ffb889a10e4 [GCFrame: 0000004d10dfde00] 0000004d10dfdf28 00007ffb889a10e4 [HelperMethodFrame_1OBJ: 0000004d10dfdf28] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object) 0000004d10dfe040 00007ffb66920d64 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken) 0000004d10dfe0d0 00007ffb6691b4bb System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) 0000004d10dfe140 00007ffb672601d1 System.Threading.Tasks.Task.InternalWait(Int32, System.Threading.CancellationToken) 0000004d10dfe210 00007ffb6725cfa7 System.Threading.Tasks.Task'1[[System.__Canon, mscorlib]]. GetResultCore(Boolean) 0000004d10dfe250 00007ffb18172a1b WindowsFormsApp4.Form1.button1_Click(System.Object, System.EventArgs) [E:\net5\ConsoleApp1\WindowsFormsApp4\Form1.cs @ 26] 0000004d10dfe2b0 00007ffb3a024747 System.Windows.Forms.Control.OnClick(System.EventArgs) 0000004d10dfe2f0 00007ffb3a027b83 System.Windows.Forms.Button.OnClick(System.EventArgs) 0000004d10dfe340 00007ffb3a837231 System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs) 0000004d10dfe400 00007ffb3a7e097d System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, int32) 0000004d10dfe480 00007ffb3a0311cc System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef) 0000004d10dfe540 00007ffb3a0b0c97 System.Windows.Forms.ButtonBase.WndProc(System.Windows.Forms.Message ByRef) 0000004d10dfe5c0 00007ffb3a0b0be5 system.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef) 0000004d10dfe5f0 00007ffb3a030082 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr) 0000004d10dfe690 00007ffb3a765a02 DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int64, Int32, Int64, Int64) 0000004d10dfe9d0 00007ffb776d221e [InlinedCallFrame: 0000004d10dfe9d0] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef) 0000004d10dfe9d0 00007ffb3a0b9489 [InlinedCallFrame: 0000004d10dfe9d0] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef) 0000004d10dfe9a0 00007ffb3a0b9489 DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef) 0000004d10dfea60 00007ffb3a046661 System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop( IntPtr, Int32, Int32) 0000004d10dfeb50 00007ffb3a045fc7 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext) 0000004d10dfebf0 00007ffb3a045dc2 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext) 0000004d10dfec50 00007ffb181708e2 WindowsFormsApp4.Program.Main() [E:\net5\ConsoleApp1\WindowsFormsApp4\Program.cs @ 19] 0000004d10dfee78 00007ffb776d6923 [GCFrame: 0000004d10dfee78] Stack çıktısından ana iş parçacığı nihayet Monitor.ObjWait'te Task.Result altında takılı kaldı, bu da son jsonString'in henüz alınmadığı anlamına geliyor, bu çok garip, birkaç dakika geçti, ağda bir sorun mu var? Ağım 100M ateş gücüyle dolu...
2) jsonString nereye gitti? Yönetilen yığında jsonString bulunursa, programda sonuç gecikmesine neden olan bir şey olduğu anlamına gelir, aşağıdaki şekilde gösterildiği gibi !dumpheap -type String -min 8500 + !do 000001f19002fcf0 komutunu kullanın:
Şekilden açıkça HTML'nin geri döndüğü anlaşılıyor, çünkü hepsi geri döndü, neden Task.Result henüz bitmedi? Sonraki adım, bu html'yi kimin tuttuğuna bakmak ve !gcroot kullanmak.
0:000> !gcroot 000001f19002fcf0 Konu 5a10: 0000004d10dfe250 00007ffb18172a1b WindowsFormsApp4.Form1.button1_Click(System.Object, System.EventArgs) [E:\net5\ConsoleApp1\WindowsFormsApp4\Form1.cs @ 26] rbp+10: 0000004d10dfe2b0 -> 000001f180007f78 WindowsFormsApp4.Form1 -> 000001f180070d68 Sistem.BileşenModel.Olay HandlerListesi -> 000001f180071718 System.ComponentModel.EventHandlerList+ListEntry -> 000001f1800716d8 Sistem.EventHandler -> 000001f1800716b0 System.Windows.Forms.ApplicationContext -> 000001f180071780 Sistem.Olay Yöneticisi -> 000001f18006ab38 System.Windows.Forms.Application+ThreadContext -> 000001f18006b140 System.Windows.Forms.Application+MarshalingControl -> 000001f18016c9c8 System.Collections.Queue -> 000001f18016ca00 Sistem.Nesne[] -> 000001f18016c948 System.Windows.Forms.Control+ThreadMethodEntry -> 000001f18016c8b8 Sistem.Nesne[] -> 000001f1800e6f80 Sistem. Aksiyon -> 000001f1800e6f60 System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner -> 000001f1800a77d0 WindowsFormsApp4.Form1+<GetJsonAsync>d__2 -> 000001f1800b4e50 System.Threading.Tasks.Task'1[[System.String, mscorlib]] -> 000001f19002fcf0 Sistem.Dizisi
1 benzersiz kök buldum (koş! GCRoot -tüm kökleri görmek için). Çıktı sonuçlarından anlaşılıyor ki, bu System.String nihayet 5a10 iş parçacığından WindowsFormsApp4.Form1 tarafından tutuluyor ve !t ile 5a10 iş parçacığı ne olduğunu doğrulayabilirsiniz.
0:000> !t Kilitle ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 5a10 000001f1f1b01200 2026020 Önleyici 000001F1800E70E8:000001F1800E7FD0 000001f1f1ad5b90 0 STA 2 2 712c 000001f1f1b2a270 2b20 Önleyici 00000000000000000:000000000000000000 000000000 000001f1f1ad5b90 0 MTA (Finalizer) Dedim ki, 5a10 ana konu çıktı, gerçekten biraz kafa karıştırıcıydı, ana ip sıkışmıştı ve ip ana iplikle tutuluyordu, tamamen açıklanamazdı.
3) Çıkış noktalarına bak Yine de bu referans zincirini sakin bir şekilde düşündüğümde burada bir Kuyruk olduğunu gördüm: -> 000001f18016c9c8 System.Collections.Queue, bir fikrim var, Kuyruğun içine bir kesme noktası koyarak kaynak kodunu hata ayıklayabilirim ve araç DnSpy kullanıyor, sadece yapın.
Şekilden de görebileceğiniz gibi, Kuyruk girildiğinde 10. iş parçacığı kullanılır, bu da dizinin ana iş parçacığı tarafından henüz tutulmadığı anlamına gelir.
Diyagramdan, sürekli görevin nihayet WindowsFormsSynchronizationContext.Post tarafından Kontrol altında Kuyruk içine planlandığı ve bu Kuyruğun verilerinin UI iş parçacığı tarafından çalıştırılması gerektiği görülebilir; bu nedenle aşağıdaki diyalog vardır:
Ana Konu: görev kardeşim, ne zaman bitireceksin? Sinyali tamamlamanı bekliyorum?
görev:Kardeşim, beni idam etmezsen, nasıl bitirebilirim?
Ana konu:Aman...
Özetle: devam görevi ana iş parçacığının yürütülmesini bekleyen kuyruk noktasına ulaştı ve şu anda ana iş parçacığı sersemlemiş ve devam görevinin tamamlanmasını bekliyor=doğru, sorun burada: Devam görevi nasıl Tamamlanmaz=doğru? Yani böyleler.
Üç: Nasıl çözülür
Neden ve sonuç bilgisi olan bu çatlama yöntemi basittir ve kabaca iki türe ayrılır.
1. Devam görevinin kuyruğa bırakılması yasaktır
Bu yolu kesmek için, ipucu havuzunun görevi kendi başına bitirmesine izin verilmesi, böylece UI iş parçacığı görevin tamamlandığını hissedebilir ve son olarak UI iş parçacığı son html'yi alabilir; bu html await'ten sonra ConfigureAwait(false) eklenecektir, aşağıdaki gibi:
2. Ana konuyu engelle
Ana iş parçacığı engellenmemişse, ana iş parçacığı Control.Queue'da yürütülmesi gereken görevleri serbestçe elde edebilir ve yöntem çok basittir, sadece GetJsonAsync'ten önce beklemeyi ekleyin.
Üç: Özet
Sonuç olarak, kendi pratik pratiğinizi daha fazla yapmalısınız, teorik bilgi başkaları tarafından size zorla aşılanıyor, doğru ya da yanlış olsun, aslında kalbinizde bir dip yok, pratik doğrulama aslında size ait olan şeydir ve unutmak zor, sonuçta gerçekten deneyimlemiş, pratik yapmış ve doğrulamışsınız.
Özgün:Bağlantı girişi görünür.
|
Önceki:Docker, konteyner işlemcisi, bellek ve diğer kaynakları değiştirir ve kısıtlarÖnümüzdeki:JS dizileri, her şeyin farkı ve kullanımı
|