1: Pozadí
1. Vyprávět příběhy
Před pár dny, když jsem indexoval článek o .NET Advanced Debugging na github, našel jsem zajímavý komentář, podrobnosti najdete v článku, screenshot je následující:
Pravděpodobně to znamená, že spuštění Task.Result v hlavním vlákně Winformu způsobí zablokování, také jsem se podíval na odkaz na obrázku, Stephen je absolutní šéf, ale tento článek je hlavně o indoktrinaci velkého odstavce textu a ve skutečnosti vám nedovolí vidět to, co vidíte, takže to budu analyzovat z pohledu windbg.
2: Analýza Windbg
1. Opravdu to bude zablokované?
Samozřejmě, že jsem Winform nehrál mnoho let a nemůžu přijít na to, jestli to bude moct, alespoň ne na konzoli.
Kód je velmi jednoduchý, spustíte program, kliknete klik a opravdu, rozhraní se zasekne, což je docela neuvěřitelné.
2. Hledejte příčinu patové situace
Pak si pospěšte a připojte windbg k procesu, abyste to zjistili.
1) Podívejte se na hlavní vlákno Rozhraní je nereagující, takže hlavní vlákno je přirozeně zaseklé, takže je potřeba se podívat, co hlavní vlákno právě dělá. Použijte příkaz ~0s + !clrstack.
0:000> !clrstack ID vlákna operačního systému: 0x5a10 (0) Dětská stránka pro IP volání SP 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] Z výstupu zásobníku je hlavní vlákno konečně zaseknuté na Monitor.ObjWait pod Task.Result, což znamená, že ještě nenačetlo poslední jsonString, což je velmi zvláštní, už je to několik minut, je problém se sítí? Moje síť je 100M plná palebné síly...
2) Kam zmizel jsonString? Pokud je jsonString nalezen na spravované haldě, znamená to, že v programu je něco, co způsobuje výsledné zpoždění, použijte příkaz !dumpheap -type String -min 8500 + !do 000001f19002fcf0 pro zobrazení, jak je znázorněno na obrázku níže:
Z obrázku je jasně vidět, že html je zpět, protože je všechno zpět, proč Task.Result ještě neskončil? Dalším krokem je zjistit, kdo drží tento html, a použít !gcroot.
0:000> !gcroot 000001f19002fcf0 Vlákno 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 System.ComponentModel.EventHandlerList -> 000001f180071718 System.ComponentModel.EventHandlerList+ListEntry -> 000001f1800716d8 System.EventHandler -> 000001f1800716b0 System.Windows.Forms.ApplicationContext -> 000001f180071780 System.EventHandler -> 000001f18006ab38 System.Windows.Forms.Application+ThreadContext -> 000001f18006b140 System.Windows.Forms.Application+MarshalingControl -> 000001f18016c9c8 System.Collections.Queue -> 000001f18016ca00 System.Object[] -> 000001f18016c948 System.Windows.Forms.Control+ThreadMethodEntry -> 000001f18016c8b8 System.Object[] -> 000001f1800e6f80 System. Akce -> 000001f1800e6f60 System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner -> 000001f1800a77d0 WindowsFormsApp4.Form1+<GetJsonAsync>d__2 -> 000001f1800b4e50 System.Threading.Tasks.Task'1[[System.String, mscorlib]] -> 000001f19002fcf0 System.String
Našel jsem 1 unikátní kořen (běž '! GCRoot -all' pro zobrazení všech kořenů). Z výstupních výsledků je tento System.String nakonec uložen ve WindowsFormsApp4.Form1 vlákna 5a10 a můžete použít !t k ověření, co je vlákno 5a10.
0:000> !t Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 5a10 000001f1f1b01200 2026020 Preemptive 000001F1800E70E8:000001F1800E7FD0 000001f1f1ad5b90 0 STA 2 2 712c 0000001f1f1b2a270 2b220 Preemptive 00000000000000000:000000000000000000000 00000001f1f1ad5b90 0 MTA (Finalizer) Řekl jsem si, 5a10 se ukázalo být hlavním vláknem, bylo to opravdu trochu matoucí, hlavní vlákno bylo zaseklé a nit držela hlavní nit, úplně nevysvětlitelné.
3) Hledejte průlomové body Stále se vracím a klidně přemýšlím o tomto referenčním řetězci a zjistil jsem, že tady je fronta: -> 000001f18016c9c8 System.Collections.Queue, mám nápad, mohu debugovat zdrojový kód tak, že do fronty vložím breakpoint, a nástroj používá DnSpy, prostě to udělej.
Jak je vidět na obrázku, při vstupu do fronty se používá vlákno 10, což znamená, že řetězec v tuto chvíli ještě není držen hlavním vláknem.
Z diagramu je vidět, že kontinuální úkol je nakonec WindowsFormsSynchronizationContext.Post naplánován do fronty pod Ovládání a data v této frontě musí být vykonána vláknem UI, takže je zde následující dialog:
Hlavní vlákno: Úkol, bratře, kdy ho dokončíš? Čekám, až dokončíš signál?
úkol:Bratře, když mě nepopravíš, jak to mám ukončit?
Hlavní vlákno:Ach...
Shrnuto: úkol pokračování dosáhl fronty čekající na spuštění hlavního vlákna, a v tuto chvíli je hlavní vlákno omráčené a čeká na dokončení úkolu pokračování =true, problém je zde: jak může být úkol pokračování nespuštěn Complete=true? Takže jsou takové.
Tři: Jak to rozluštit
S vědomím příčiny a následku je tato metoda praskání jednoduchá, přibližně rozdělená do dvou typů.
1. Je zakázáno vložit pokračovací úkol do fronty
K ukončení této cesty znamená nechat thread pool ukončit úkol sám, aby vlákno UI mohlo vycítit, že úkol byl dokončen, a nakonec vlákno UI může získat finální html, což je přidání ConfigureAwait(false) po await, a to následovně:
2. Zablokujte hlavní vlákno
Pokud hlavní vlákno není zablokováno, může hlavní vlákno volně získat úkoly, které je třeba v Control.Queue spustit, a metoda je velmi jednoduchá, stačí přidat await před GetJsonAsync.
Tři: Shrnutí
Závěrem je, že pokud budete více praktikovat sami, teoretické znalosti vám vštěpují ostatní, ať už jsou správné nebo ne, ve skutečnosti nemáte dno v srdci, praktické ověření je to, co vám skutečně patří, a je těžké zapomenout, koneckonců jste skutečně zažili, procvičili a ověřili.
Původní:Přihlášení k hypertextovému odkazu je viditelné.
|