1: 背景
1. 物語を語ること
数日前、.NET Advanced Debuggingの記事をgithubにインデックス化したところ、興味深いコメントを見つけました。詳細は記事をご覧ください。スクリーンショットは以下の通りです:
おそらくWinformのメインスレッドでTask.Resultを実行するとデッドロックが起きるという意味でしょう。写真の参考リンクも見ましたが、Stephenは絶対的なボスですが、この記事は主に大きな段落のテキストの洗脳についてで、実際に見えるものはあまり見えないので、Windbgの視点から分析します。
2:風の分析
1. 本当にデッドロックになるのか?
もちろん、私は何年もWinformをプレイしていませんし、少なくともコンソール版ではうまくいくかどうかも分かりません。
コードは非常にシンプルで、プログラムを実行してクリックをクリックすると、案の定、インターフェースが固まってしまい、それが少し驚きです。
2. 膠着状態の原因を探す
次に、すぐにWindbgをプロセスに付けて確かめましょう。
1) メインスレッドを見てください インターフェースが反応しないため、当然メインスレッドが詰まっているので、この時点でメインスレッドが何をしているかを確認する必要があります。 コマンド ~0s + !clrstack を使ってください。
0:000> !clrstack OSスレッドID:0x5a10(0) チャイルドSPのIPコールサイト 0000004d10dfde00 00007ffb889a10e4 [GCFrame: 0000004d10dfde00] 0000004d10dfdf28 00007ffb889a10e4 [HelperMethodFrame_1OBJ: 0000004d10dfdf28] System.Threading.Monitor.ObjWait(ブール、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] スタック出力から、メインスレッドが最終的にMonitor.ObjWaitのTask.Resultで止まってしまい、まだ最後のjsonStringを取得できていないという意味です。非常に奇妙です。数分経っているのですが、ネットワークに問題があるのでしょうか? 俺の網は100Mいっぱいの火力だ...
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 -> 0000001f1800a77d0 WindowsFormsApp4.Form1+<GetJsonAsync>d__2 -> 000001f1800b4e50 System.Threading.Tasks.Task'1[[System.String, mscorlib]] -> 000001f19002fcf0 システム.文字列
ユニークな根源を1つ見つけました(走って! GCRoot -all' で全ての根を見ることができます)。 出力結果から、このSystem.Stringは最終的に5a10スレッドのWindowsFormsApp4.Form1に保持されており、!tを使って5a10がどのスレッドかを確認できます。
0:000>!t ロック ID OSID ThreadOBJ State GC Mode GC Alloc コンテキストドメインカウント apt exception 0 1 5a10 000001f1f1b01200 2026020 プリエンプティブ 0000001F1800E70E8:000001F1800E7FD0 000001f1f1f1ad5b90 0 STA 2 2 712c 000001f1f1b1b2a270 2b220 プリエンプティブ 0000000000000000:00000000000000000000 000001f1f1f1ad5b90 0 MTA(フィニライザー) すると5a10がメインスレッドであることが判明しました。本当に少し混乱しました。メインスレッドが詰まっていて、糸がメインスレッドに引っかかっていたのが全く説明できません。
3) ブレイクポイントを探す この参照チェーンを冷静に考え直し、ここにキューがあることに気づきました:-> 000001f18016c9c8 System.Collections.Queue。アイデアがあります。キューにブレークポイントを入れてソースコードをデバッグできるし、ツールはDnSpyを使っています。とにかくやってみて。
図からもわかるように、キューに入る際にスレッド10が使われており、この時点ではメインスレッドがまだその文字列を保持していないことを意味します。
図から、連続タスクが最終的にコントロール下のキューに WindowsFormsSynchronizationContext.Post スケジューリングされ、このキュー内のデータはUIスレッドによって実行される必要があるため、以下のダイアログが表示されます。
メインスレッド:タスク兄弟、いつ終わらせるんだ? 信号を完成させるのを待っているの?
タスク:兄さん、処刑しなければどうやって終わらせるんだ?
メインスレッド:おや。。。
まとめると、継続タスクはメインスレッドの実行を待つキューに到達し、その時点でメインスレッドはスタン状態にあり、継続タスクの完了を待っている状態です=true。問題はここにあります:なぜ継続タスクが実行されないのか?Complete=true? だから彼らはこういう感じなんだ。
三つ目:どうやってそれを解くか
原因と結果を知ると、この割れ方はシンプルで、大まかに二つのタイプに分けられます。
1. 継続タスクをキューにドロップすることは禁止されています
この経路を遮断するには、スレッドプールがタスクを自動で終了させ、UIスレッドがタスク完了を感知できるようにし、最終的にUIスレッドが最終htmlを取得します。これはawait後にConfigureAwait(false)を追加することです。以下の通りです。
2. メインスレッドをブロックする
メインスレッドがブロックされていなければ、メインスレッドはControl.Queueで実行すべきタスクを自由に取得でき、メソッドは非常にシンプルで、GetJsonAsyncの前にawaitを追加するだけです。
三つ目:概要
結論は、自分自身の実践をもっと実践することです。理論的知識は他者によって強制的に植え付けられ、それが正しいか間違っているかに関わらず、実際には心に底はありません。実践的な検証こそが本当に自分のものであり、忘れるのは難しいことです。結局のところ、あなたは本当に経験し、練習し、検証してきたのですから。
翻訳元:ハイパーリンクのログインが見えます。
|