1: Antecedentes
1. Contar historias
Hace unos días, cuando indexé el artículo sobre la depuración avanzada de .NET en github, encontré un comentario interesante, consulta el artículo para más detalles, la captura de pantalla es la siguiente:
Probablemente significa que ejecutar Task.Result bajo el hilo principal de Winform causará un estancamiento; también miré el enlace de referencia en la imagen, Stephen es el jefe absoluto, pero este artículo trata principalmente sobre la indoctrinación de un párrafo extenso de texto, y realmente no te deja ver lo que ves, así que lo analizaré desde la perspectiva de windbg.
2: Análisis de Windbg
1. ¿Realmente quedará bloqueado?
Por supuesto, no he jugado a winform en muchos años, y no sé si lo hará, al menos no en consola.
El código es muy sencillo, ejecutas el programa, haces clic y clic y, efectivamente, la interfaz se queda atascada, lo cual es bastante increíble.
2. Buscar la causa del estancamiento
Después, date prisa y adjunta windbg al proceso para averiguarlo.
1) Mira el hilo principal La interfaz no responde, así que naturalmente el hilo principal está atascado, así que tienes que fijarte en qué está haciendo el hilo principal en este momento. Usa el comando ~0s + !clrstack.
0:000> !clrstack ID del hilo del sistema operativo: 0x5a10 (0) Sitio de Llamadas IP SP Hijo 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(Booleano) 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] Desde la salida de la pila, el hilo principal finalmente se queda atascado en Monitor.ObjWait bajo Task.Result, lo que significa que aún no ha recuperado la última jsonString, lo cual es muy extraño, han pasado varios minutos, ¿hay algún problema con la red? Mi red tiene 100 metros llenos de potencia de fuego...
2) ¿Dónde se fue jsonString? Si jsonString se encuentra en el heap gestionado, significa que hay algo en el programa que hace que el resultado se retrase, usa el comando !dumpheap -type String -min 8500 + !do 000001f19002fcf0 para ver, como se muestra en la figura de abajo:
Por la figura, se puede ver claramente que html ha vuelto, y ya que todo ha vuelto, ¿por qué Task.Result no ha terminado todavía? El siguiente paso es ver quién tiene este html y usar !gcroot.
0:000> !gcroot 000001f19002fcf0 Hilo 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.FilerList+ListListList -> 000001f1800716d8 System.EventHandler -> 000001f1800716b0 System.Windows.Forms.ApplicationContext -> 000001f180071780 Sistema.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 Sistema. Acción -> 000001f1800e6f60 System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner -> 000001f1800a77d0 WindowsFormsApp4.Form1+<GetJsonAsync>d__2 -> 000001f1800b4e50 System.Threading.Tasks.Task'1[[System.String, mscorlib]] -> 000001f19002fcf0 Sistema.String
Encontré 1 raíz única (¡corre! GCRoot -all' para ver todas las raíces). A partir de los resultados de la salida, esta System.String está finalmente en manos de WindowsFormsApp4.Form1 del hilo 5a10, y puedes usar !t para verificar qué hilo es 5a10.
0:000> ¡Cerradura ID OSID ThreadOBJ Estado GC Modo GC Alloc Contexto Dominio Excepción Apt 0 1 5a10 000001f1f1b01200 2026020 Preemptivo 000001F1800E70E8:000001F1800E7FD0 000001f1f1ad5b90 0 STA 2 2 712c 000001f1f1b2a270 2b220 Preemptivo 000000000000000:00000000000000000000000 0000001f1f1f1ad5b90 0 MTA (Finalizer) Voy, 5a10 resultó ser el hilo principal, fue un poco confuso, el hilo principal estaba atascado y la cuerda estaba sujeta por el hilo principal, completamente inexplicable.
3) Buscar puntos de ruptura Aun así, pensando en esta cadena de referencia con calma, descubrí que aquí hay una cola: -> 000001f18016c9c8 System.Collections.Queue, tengo una idea, puedo depurar el código fuente colocando un punto de interrupción en la cola, y la herramienta usa DnSpy, simplemente hazlo.
Como puedes ver en la figura, al entrar en la cola, se usa el hilo 10, lo que significa que la cadena aún no está sostenida por el hilo principal en ese momento.
En el diagrama, se puede ver que la tarea continua finalmente se programa por WindowsFormsSynchronizationContext.Post en la Cola bajo Control, y los datos en esta Cola deben ejecutarse mediante el hilo de la interfaz, por lo que hay el siguiente diálogo:
Hilo principal: Prueba hermano, ¿cuándo terminarás de ejecutarlo? ¿Estoy esperando a que completes la señal?
tarea:Hermano, si no me ejecutas, ¿cómo voy a acabar con esto?
Hilo principal:Oh...
En resumen: la tarea de continuación ha llegado a la cola esperando a que se ejecute el hilo principal, y en ese momento, el hilo principal está aturdido y ha estado esperando a que se complete la tarea de continuación=true, el problema está aquí: ¿cómo puede la tarea de continuación no ejecutarse Completado=true? Así que son así.
Tres: Cómo descifrarlo
Conociendo la causa y efecto, este método de agrietamiento es sencillo, dividido aproximadamente en dos tipos.
1. Está prohibido incluir una tarea de continuación en una cola
Para cortar este camino, la implicación es dejar que el grupo de hilos termine la tarea por sí mismo, de modo que el hilo de la interfaz pueda detectar que la tarea ha sido completada, y finalmente el hilo de la interfaz pueda obtener el html final, que es añadir ConfigureAwait(false) tras await, de la siguiente manera:
2. Bloquear el hilo principal
Si el hilo principal no está bloqueado, entonces el hilo principal puede obtener libremente las tareas que deben ejecutarse en Control.Queue, y el método es muy sencillo: simplemente añadir await antes de GetJsonAsync.
Tres: Resumen
La conclusión es que hagas más de tu propia práctica práctica, el conocimiento teórico te lo inculcan a la fuerza otros lo inculcan, sea correcto o incorrecto, de hecho, no tienes fondo en el corazón, la verificación práctica es lo que realmente te pertenece, y es difícil olvidar, al fin y al cabo, que realmente has experimentado, practicado y verificado.
Texto original en:El inicio de sesión del hipervínculo es visible.
|