1: Передумови
1. Розповідати історії
Кілька днів тому, коли я індексував статтю про .NET Advanced Debugging на github, я знайшов цікавий коментар, деталі дивіться статтю, скріншот виглядає так:
Ймовірно, це означає, що виконання Task.Result у основній гілці Winform призведе до глухого кута, я також переглянув посилання на зображення, Стівен — абсолютний бос, але ця стаття здебільшого про індоктринацію великого абзацу тексту, і вона не дає побачити те, що бачиш ти, тому я проаналізую це з точки зору Windbg.
2: Аналіз Windbg
1. Чи справді все буде заблоковано?
Звісно, я не грав у winform багато років і не можу зрозуміти, чи буде він, принаймні не на консолі.
Код дуже простий: запусти програму, клацнуть-клацає, і справді, інтерфейс зависає, що трохи неймовірно.
2. Знайдіть причину глухого кута
Далі поспішайте і прикріпіть Windbg до процесу, щоб дізнатися.
1) Подивіться на основну тему Інтерфейс не відгукується, тому основний потік застряг, і потрібно подивитися, що робить основний потік у цей момент. Використовуйте команду ~0s + !clrstack.
0:000> !clrstack Ідентифікатор потоку ОС: 0x5a10 (0) Дочірній сайт IP-дзвінків 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 (булевий) 0000004d10dfe250 00007ffb18172a1b WindowsFormsApp4.Form1.button1_Click(System.Object, System.EventArgs) [E:\net5\ConsoleApp1\WindowsFormsApp4\Form1.cs @ 26] 000004d10dfe2b0 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) 000004d10dfe400 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] З виводу стеку основний потік остаточно зависає на Monitor.ObjWait у Task.Result, тобто він ще не отримав останній jsonString, що дуже дивно, минуло вже кілька хвилин, чи є проблема з мережею? Моя сітка — 100 метрів вогневої потужності...
2) Куди подівся jsonString? Якщо jsonString знаходиться в керованій купі, це означає, що в програмі є щось, що створює затримку результату, використовуйте команду !dumpheap -type String -min 8500 + !do 000001f19002fcf0 для перегляду, як показано на рисунку нижче:
З рисунка чітко видно, що html повернувся, оскільки все повернулося, чому Task.Result ще не завершився? Наступний крок — з'ясувати, хто володіє цим html, і використати !gcroot.
0:000> !gcroot 000001f19002fcf0 Нитка 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 Система. Об'єкт[] -> 000001f18016c948 System.Windows.Forms.Control+ThreadMethodEntry -> 000001f18016c8b8 Система. Об'єкт[] -> 000001f1800e6f80 Система. Дія -> 000001f1800e6f60 System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner -> 000001f1800a77d0 WindowsFormsApp4.Form1+<GetJsonAsync>d__2 -> 000001f1800b4e50 System.Threading.Tasks.Task'1[[System.String, mscorlib]] -> 000001f19002fcf0 System.String
Знайшов 1 унікальний корені (біжи! GCRoot -all', щоб побачити всі корені). З результатів виходу цей рядок System.String нарешті зберігається у WindowsFormsApp4.Form1 потоку 5a10, і ви можете використати !t, щоб перевірити, який потік 5a10.
0:000> Замок ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 5a10 000001f1f1b01200 2026020 Превентивний 000001F1800E70E8:000001F1800E7FD0 000001f1f1ad5b90 0 STA 2 2 712c 000001f1f1b2a270 2b220 Преемптивний 0000000000000000000000000000000000000 000000001f1f1ad5b90 0 MTA (Фіналізатор) Я йду, 5a10 виявився основною ниткою, це було трохи заплутано, основна нитка застрягла, а нитка трималася за основну нитку, що було абсолютно незрозуміло.
3) Шукайте точки прориву Все ще повертаючись і спокійно розмірковуючи над цим посиланням, я виявив, що тут є черга: -> 000001f18016c9c8 System.Collections.Queue, у мене є ідея, я можу налагодити вихідний код, розмістивши точку зупину в черзі, і інструмент використовує DnSpy, просто зроби це.
Як видно на рисунку, при вході в чергу використовується потік 10, що означає, що рядок ще не утримується основним потоком у цей момент.
З діаграми видно, що безперервне завдання нарешті планується WindowsFormsSynchronizationContext.Post в черзі під контролем, і дані в цій черзі потрібно виконати через потік інтерфейсу, тому є наступний діалог:
Головна тема: Завдання, брате, коли ти закінчиш його виконання? Я чекаю, поки ти завершиш сигнал?
завдання:Брате, якщо ти мене не стратиш, як я це закінчу?
Основна тема:Ах...
Підсумовуючи: завдання продовження досягло черги, що чекає на виконання основного потоку, і в цей момент основний потік перебуває в глухому стані та чекає завершення завдання продовження =true, проблема в тому, як завдання продовження не може бути виконане Complete=true? Отже, вони ось такі.
Третє: як його розгадати
Знаючи причину і наслідок, цей метод тріщини простий і приблизно поділяється на два типи.
1. Заборонено ставити завдання продовження у чергу
Щоб відрізати цей шлях, слід дозволити пулу потоку завершити завдання самостійно, щоб потік UI міг відчути, що завдання виконано, і нарешті потік UI отримав фінальний html, тобто додати ConfigureAwait(false) після await, наступним чином:
2. Заблокувати основну нитку
Якщо основний потік не заблокований, тоді основний потік може вільно отримувати завдання, які потрібно виконати в Control.Queue, і метод дуже простий — просто додайте waitit перед GetJsonAsync.
Третє: Підсумок
Висновок — займатися власною практичною практикою, теоретичні знання нав'язують тобі інші, незалежно від того, правильні вони чи ні, насправді у тебе немає дна в серці, практична перевірка — це те, що справді належить тобі, і важко забути, адже ти справді пережив, практикував і перевіряв.
Оригінальний:Вхід за гіперпосиланням видно.
|