1 : Contexte
1. Raconter des histoires
Il y a quelques jours, lorsque j’ai indexé l’article sur le débogage avancé .NET sur github, j’ai trouvé un commentaire intéressant, voir l’article pour plus de détails, la capture d’écran est la suivante :
Cela signifie probablement que l’exécution de Task.Result sous le fil principal de Winform provoquera une impasse, j’ai aussi regardé le lien de référence sur la photo, Stephen est le boss absolu, mais cet article porte principalement sur l’endoctrinement d’un long paragraphe de texte, et il ne vous permet pas vraiment de voir ce que vous voyez, donc je vais l’analyser du point de vue de windbg.
2 : Analyse du Windbg
1. Est-ce qu’il sera vraiment bloqué ?
Bien sûr, je n’ai pas joué à Winform depuis de nombreuses années, et je n’arrive pas à savoir si ça le fera, du moins pas sur console.
Le code est très simple, lance le programme, clique et clique, et effectivement, l’interface est bloquée, ce qui est un peu incroyable.
2. Chercher la cause de l’impasse
Ensuite, dépêchez-vous d’ajouter du windbg au procédé pour en savoir plus.
1) Regardez le fil principal L’interface ne répond pas, donc naturellement le fil principal est bloqué, il faut donc regarder ce que fait le fil principal à ce moment-là. Utilisez la commande ~0s + !clrstack.
0:000> !clrstack ID du fil OS : 0x5a10 (0) Site d’appel IP SP enfant 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 (Booléen) 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] D’après la sortie de la pile, le thread principal est finalement bloqué sur Monitor.ObjWait sous Task.Result, ce qui signifie qu’il n’a pas encore récupéré la dernière jsonString, ce qui est très étrange, cela fait plusieurs minutes, y a-t-il un problème avec le réseau ? Mon filet est à 100 m de puissance de feu...
2) Où est passé jsonString ? Si jsonString est trouvé sur le tas géré, cela signifie qu’il y a quelque chose dans le programme qui retarde le résultat, utilisez la commande !dumpheap -type String -min 8500 + !do 000001f19002fcf0 pour le voir, comme montré dans la figure ci-dessous :
D’après la figure, on voit clairement que le HTML est de retour, puisque tout est revenu, pourquoi Task.Result n’est-il pas encore terminé ? L’étape suivante consiste à voir qui détient ce html et à utiliser !gcroot.
0:000> !gcroot 000001f19002fcf0 Fil 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 Système.EventHandler -> 000001f1800716b0 System.Windows.Forms.ApplicationContext -> 000001f180071780 Système.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 Système. Action -> 000001f1800e6f60 System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner -> 000001f1800a77d0 WindowsFormsApp4.Form1+<GetJsonAsync>d__2 -> 000001f1800b4e50 System.Threading.Tasks.Task'1[[System.String, mscorlib]] -> 000001f19002fcf0 System.String
J’ai trouvé 1 racine unique (run ' ! GCRoot -all to see all roots). D’après les résultats de sortie, ce System.String est finalement détenu par WindowsFormsApp4.Form1 du thread 5a10, et vous pouvez utiliser !t pour vérifier quel thread est 5a10.
0:000> !t Serrure ID OSID ThreadOBJ State GC Mode GC Alloc Contexte Domain Count Apt Exception 0 1 5a10 000001f1f1b01200 2026020 Préemptif 000001F1800E70E8:000001F1800E7FD0 000001f1f1ad5b90 0 STA 2 2 712c 000001f1f1b2a270 2b220 Préemptif 000000000000000:00000000000000000000000 000001f1f1f1ad5b90 0 MTA (Finalizer) Je me dis, 5a10 s’est avéré être le fil principal, c’était vraiment un peu confus, le fil principal était coincé, et la ficelle était maintenue par le fil principal, totalement inexplicable.
3) Chercher des points de rupture En repensant calmement à cette chaîne de référence, j’ai découvert qu’il y a une file d’attente ici : -> 000001f18016c9c8 System.Collections.Queue, j’ai une idée, je peux déboguer le code source en plaçant un point d’arrêt dans la file, et l’outil utilise DnSpy, il suffit de le faire.
Comme vous pouvez le voir sur la figure, en entrant dans la file d’attente, le thread 10 est utilisé, ce qui signifie que la chaîne n’est pas encore détenue par le thread principal à ce moment-là.
D’après le diagramme, on peut voir que la tâche continue est enfin planifiée par WindowsFormsSynchronizationContext.Post dans la file sous contrôle, et que les données de cette file doivent être exécutées par le thread UI, ce qui explique le dialogue suivant :
Fil principal: Frère de la tâche, quand vas-tu finir de l’exécuter ? J’attends que tu termines le signal ?
tâche:Frère, si tu ne m’exécutes pas, comment pourrais-je finir ?
Fil principal :Oh...
En résumé : la tâche de continuation est arrivée dans la file d’attente de l’exécution du fil principal, et à ce moment-là, le fil principal est stupéfait et attend que la tâche de continuation soit terminée=vrai, le problème est ici : comment la tâche de continuation peut-elle ne pas être exécutée Terminé=vrai ? Donc ils sont comme ça.
Trois : Comment le percer
Connaissant la cause et l’effet, cette méthode de fissuration est simple, grossièrement divisée en deux types.
1. Il est interdit de placer une tâche de continuation dans une file d’attente
Pour couper ce chemin, l’implication est de laisser le pool de threads terminer la tâche de lui-même, afin que le thread UI puisse sentir que la tâche a été terminée, et enfin le thread UI puisse obtenir le HTML final, qui consiste à ajouter ConfigureAwait(false) après await, comme suit :
2. Bloquez le fil principal
Si le thread principal n’est pas bloqué, alors le thread principal peut librement obtenir les tâches à exécuter dans Control.Queue, et la méthode est très simple : il suffit d’ajouter await avant GetJsonAsync.
Trois : Résumé
La conclusion est de faire davantage de pratique pratique par soi, les connaissances théoriques vous sont inculquées de force par d’autres, qu’elles soient justes ou non, en fait, vous n’avez pas de fond dans le cœur, la vérification pratique est ce qui vous appartient vraiment, et il est difficile d’oublier, après tout, que vous avez vraiment vécu, pratiqué et vérifié.
Langue source:La connexion hyperlientérée est visible.
|