이 글은 기계 번역의 미러 문서이며, 원본 기사로 바로 이동하려면 여기를 클릭해 주세요.

보기: 6277|회답: 2

[통신] [턴] 문장 Task.결과가 잠기는데, 이 코드를 어떻게 쓸 수 있겠어?

[링크 복사]
2022-8-7 21:11:58에 게시됨 | | | |
1: 배경

1. 이야기 들려주기

며칠 전, .NET 고급 디버깅 관련 문서를 github에 색인화했을 때 흥미로운 댓글을 발견했습니다. 자세한 내용은 해당 기사를 참고하세요. 스크린샷은 다음과 같습니다:



아마도 Winform의 메인 스레드에서 Task.Result를 실행하면 교착 상태가 발생할 수 있다는 뜻일 겁니다. 사진에 있는 참고 링크도 봤는데, Stephen이 절대적인 책임자입니다. 하지만 이 글은 주로 긴 문단의 세뇌에 관한 것이고, 실제로 보이는 것을 보여주지 않아 Windbg 관점에서 분석하겠습니다.

2: 윈드브 분석

1. 정말로 교착 상태가 될까?

물론 저는 Winform을 오랫동안 플레이하지 않았고, 적어도 콘솔에서는 그럴지 잘 모르겠습니다.

코드는 매우 간단합니다. 프로그램을 실행하고 클릭만 클릭하면 인터페이스가 멈춰버려서 꽤 놀랍습니다.

2. 교착 상태의 원인을 찾기

다음으로, 서둘러 WindBGG를 과정에 연결해 확인해 보세요.

1) 메인 스레드를 살펴보기
인터페이스가 반응하지 않아서 당연히 메인 스레드가 멈추는 거라, 이 시점에 메인 스레드가 무엇을 하고 있는지 확인해야 합니다. ~0s + !clrstack 명령어를 사용하세요.

0:000> !클러스택
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(불리언)
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 [인라인 콜프레임: 0000004d10dfe9d0] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
0000004d10dfe9d0 00007ffb3a0b9489 [인라인 콜프레임: 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]
스택 출력에서 메인 스레드가 Task.Result 아래 Monitor.ObjWait에서 멈춰 있는데, 이는 마지막 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: 0000004d10DF2B0
            -> 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 시스템.컬렉션.큐
            -> 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 시스템.스트링

고유 뿌리 1개를 찾았어요 (도망쳐! GCRoot -all'에서 모든 뿌리를 볼 수 있습니다).
출력 결과에 따르면, 이 System.String은 5a10 스레드의 WindowsFormsApp4.Form1에 의해 최종 저장되며, 5a10 스레드가 무엇인지 확인하기 위해 !t를 사용할 수 있습니다.

0:000> !t 잠금  
       ID OSID ThreadOBJ State GC 모드 GC Alloc 컨텍스트 도메인 카운트 apt exception
   0 1 5a10 000001f1f1f1b01200 2026020 선점 선점 000001F1800E70E8:0000001F1800E7FD0 000001f1f1f1ad5b90 0 STA
   2 2 712c 000001f1f1f1b2a270 2b220 선점 00000000000000000000000000000000000000000000000000001f1f1f1f1ad5b90 0 MTA (파이널라이저)
그래서 5a10이 메인 스레드였는데, 정말 혼란스러웠어요. 메인 스레드가 걸렸고, 줄이 메인 스레드에 잡혀 있었는데, 전혀 설명할 수 없었습니다.

3) 돌파구를 찾기
이 참조 체인을 다시 생각하며 침착하게 생각해보니, 여기 큐가 있다는 것을 발견했습니다: -> 000001f18016c9c8 System.Collections.Queue, 아이디어가 있습니다. 큐에 브레이크포인트를 넣어 소스 코드를 디버깅할 수 있고, 도구가 DnSpy를 사용하니 그냥 그렇게 하세요.



그림에서 볼 수 있듯이, 큐에 들어갈 때 스레드 10이 사용되는데, 이는 현재 메인 스레드가 해당 문자열을 보유하지 않았다는 뜻입니다.



다이어그램에서 연속 작업이 마침내 WindowsFormsSynchronizationContext.Post 제어 하의 큐에 스케줄링되며, 이 큐의 데이터는 UI 스레드에서 실행되어야 하므로 다음과 같은 대화가 있습니다:

메인 스레드: 임무 형제, 언제 끝낼 거야? 신호 완료 기다리고 있어?

과업:형, 나를 처형하지 않으면 어떻게 끝내겠어?

메인 스레드:오...

요약하자면: 이어네이션 작업이 메인 스레드 실행을 기다리며 큐에 도달했고, 이 시점에서 메인 스레드는 스턴 상태이며 계속되는 작업이 완료되기를 기다리고 있습니다=true, 문제는 다음과 같습니다: 어떻게 계속되는 작업이 실행되지 않을 수 있나요? Complete=true? 그래서 그들은 이렇습니다.

셋째: 해독 방법

원인과 결과를 알면, 이 균열 방법은 간단하며 대략 두 가지 유형으로 나뉩니다.

1. 연속 작업을 큐에 드롭하는 것은 금지되어 있습니다

이 경로를 차단하기 위해 함의는 스레드 풀이 스스로 작업을 종료하도록 하여 UI 스레드가 작업이 완료되었음을 감지할 수 있게 하고, 마지막으로 UI 스레드가 최종 html을 받아 wait 후에 ConfigureAwait(false)를 추가하도록 하는 것입니다.



2. 메인 스레드를 차단한다

메인 스레드가 차단되지 않았다면, 메인 스레드는 Control.Queue에서 실행해야 할 작업을 자유롭게 얻을 수 있으며, 메서드는 매우 간단합니다. GetJsonAsync 전에 wait를 추가하면 됩니다.



셋: 요약

결론은 자신의 실천을 더 많이 하라는 것입니다. 이론적 지식은 옳든 그르든 타인에 의해 강제로 주입됩니다. 사실 마음속에는 밑바닥이 없고, 실질적인 검증이 진정으로 당신에게 속한 것이며, 결국 경험하고 연습하며 검증했다는 사실을 잊기 어렵습니다.

원문 언어:하이퍼링크 로그인이 보입니다.





이전의:도커는 컨테이너의 CPU, 메모리 및 기타 자원을 수정하고 제한합니다
다음:JS 배열은 모든 것과 일부 모두의 차이점이자 활용 요소입니다
2022-8-8 17:01:32에 게시됨 |
배우기 시작해...
2022-9-2 11:39:19에 게시됨 |
조금 배워
면책 조항:
Code Farmer Network에서 발행하는 모든 소프트웨어, 프로그래밍 자료 또는 기사는 학습 및 연구 목적으로만 사용됩니다; 위 내용은 상업적 또는 불법적인 목적으로 사용되지 않으며, 그렇지 않으면 모든 책임이 사용자에게 부담됩니다. 이 사이트의 정보는 인터넷에서 가져온 것이며, 저작권 분쟁은 이 사이트와는 관련이 없습니다. 위 내용은 다운로드 후 24시간 이내에 컴퓨터에서 완전히 삭제해야 합니다. 프로그램이 마음에 드신다면, 진짜 소프트웨어를 지원하고, 등록을 구매하며, 더 나은 진짜 서비스를 받아주세요. 침해가 있을 경우 이메일로 연락해 주시기 바랍니다.

Mail To:help@itsvse.com