성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[양승조] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
[정성태] The Windows Registry Adventure #1: ...
[정성태] systemd for Developers I ; https:/...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - 파일의 비동기 처리 유무에 따른 스레드 상황</h1> <p> 지난 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - 닷넷에서의 진정한 비동기 호출을 가능케 하는 I/O 스레드 사용법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12250'>https://www.sysnet.pe.kr/2/0/12250</a> </pre> <br /> 다음과 같이 정리를 했는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>동기 모드로 연 경우:</span> - 비동기 코드(예: FileStream.BeginRead)를 수행해도, I/O 작업을 수행하는 동기 코드(예: FileStream.Read)를 ThreadPool의 Worker 스레드에서 호출 - I/O 완료 후 콜백 코드를 ThreadPool의 Worker 스레드에서 호출 <span style='color: blue; font-weight: bold'>비동기 모드로 연 경우:</span> - 비동기 코드(예: FileStream.BeginRead)를 수행하고 곧바로 호출자 스레드로 제어 반환 - I/O 완료 후 콜백 코드를 ThreadPool의 I/O 스레드에서 호출 </pre> <br /> 이에 대해 windbg로 검증해 보겠습니다. ^^ 예제 코드는 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12250'>지난 글</a>의 것을 활용해 우선 동기 모드로 연 파일의 Begin/End 먼저 시작할 텐데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Diagnostics; using System.IO; using System.Threading; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Debug.Assert(ThreadPool.SetMinThreads(2, 0)); Debug.Assert(ThreadPool.SetMaxThreads(4, 1)); string filePath = @"...크기가 큰 파일..."; FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); byte[] buf = new byte[1024 * 1024 * 200]; IAsyncResult result = <span style='color: blue; font-weight: bold'>fs.BeginRead</span>(buf, 0, buf.Length, (ar) =&gt; { <span style='color: blue; font-weight: bold'>Thread.Sleep(1000 * 60);</span> int bytesRead = fs.EndRead(ar); }, null); result.AsyncWaitHandle.WaitOne(); Console.ReadLine(); } } } </pre> <br /> 200MB(가 중요한 것이 아니고 어쨌든 시간이 걸릴만한 것으로) 용량의 것으로 BeginRead를 했을 때 callback 익명 메서드가 불리기 전 windbg로 연결하면 다음과 같이 스레드 목록이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 4 UnstartedThread: 0 BackgroundThread: 3 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 1d88 000001dd15b85a00 202a020 Preemptive 000001DD17791520:000001DD17791FD0 000001dd15b596b0 0 MTA 5 2 8818 000001dd15baea10 2b220 Preemptive 0000000000000000:0000000000000000 000001dd15b596b0 0 MTA (Finalizer) 9 3 2080 000001dd406596b0 1029220 Preemptive 000001DD17792428:000001DD17793FD0 000001dd15b596b0 0 MTA (Threadpool Worker) 10 4 1fc4 000001dd15c1ddb0 1029220 Preemptive 000001DD177942D8:000001DD17795FD0 000001dd15b596b0 0 MTA (Threadpool Worker) </pre> <br /> 이와 함께 각각의 콜스택을 확인하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!eestack</span> --------------------------------------------- Thread 0 Current frame: ntdll!NtWaitForMultipleObjects+0x14 Child-SP RetAddr Caller, Callee ...[생략]... 000000504aefe9e0 00007ffe5df59c9f (MethodDesc 00007ffe5db78d98 +0x2f <span style='color: blue; font-weight: bold'>System.Threading.WaitHandle.WaitOne(Int32, Boolean))</span>, calling (MethodDesc 00007ffe5db78dc0 +0 System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)) ...[생략]... 000000504aeff800 00007ffe7f04bbbd clr!_CorExeMainInternal+0xb2, calling clr!ExecuteEXE 000000504aeff860 00007ffe96c9a880 ntdll!RtlSetLastWin32Error+0x40, calling ntdll!_security_check_cookie 000000504aeff890 00007ffe7f04cda4 <span style='color: blue; font-weight: bold'>clr!CorExeMain</span>+0x14, calling clr!_CorExeMainInternal 000000504aeff8d0 00007ffe804c8c01 mscoreei!CorExeMain+0x112 000000504aeff900 00007ffe808a1560 MSCOREE!GetShimImpl+0x18, calling MSCOREE!InitShimImpl 000000504aeff910 00007ffe808aacd2 MSCOREE!CorExeMain_Exported+0x102, calling KERNEL32!GetProcAddressStub 000000504aeff930 00007ffe808aac42 MSCOREE!CorExeMain_Exported+0x72, calling MSCOREE!guard_dispatch_icall_nop 000000504aeff960 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000504aeff990 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop --------------------------------------------- Thread 5 Current frame: ntdll!NtWaitForMultipleObjects+0x14 Child-SP RetAddr Caller, Callee ...[생략]... 000000504b3ff8b0 00007ffe7f0ecac7 <span style='color: blue; font-weight: bold'>clr!FinalizerThread::WaitForFinalizerEvent</span>+0xa7, calling KERNEL32!WaitForMultipleObjectsEx 000000504b3ff8f0 00007ffe7ef6f0e3 clr!FinalizerThread::FinalizerThreadWorker+0x53, calling clr!FinalizerThread::WaitForFinalizerEvent 000000504b3ff940 00007ffe7ef17cd0 clr!ManagedThreadBase_DispatchInner+0x40 000000504b3ff980 00007ffe7ef17c43 clr!ManagedThreadBase_DispatchMiddle+0x6c, calling clr!ManagedThreadBase_DispatchInner ...[생략]... 000000504b3ffc80 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop --------------------------------------------- Thread 9 Current frame: ntdll!NtReadFile+0x14 Child-SP RetAddr Caller, Callee 000000504b7ff140 00007ffe94798a53 KERNELBASE!ReadFile+0x73, calling ntdll!NtReadFile ...[생략]... 000000504b7ff370 00007ffe5df1e637 (MethodDesc 00007ffe5db7cde8 +0xe7 <span style='color: blue; font-weight: bold'>System.IO.FileStream.Read</span>(Byte[], Int32, Int32)), calling (MethodDesc 00007ffe5db7d070 +0 System.IO.FileStream.ReadCore(Byte[], Int32, Int32)) ...[생략]... 000000504b7ffca0 00007ffe7f0e70dc clr!Thread::SetApartment+0x1bf, calling clr!Thread::GetApartment 000000504b7ffcc0 00007ffe7ef17947 clr!PerAppDomainTPCountList::GetAppDomainIndexForThreadpoolDispatch+0x97 000000504b7ffd00 00007ffe7ef17897 clr!ThreadpoolMgr::ExecuteWorkRequest+0x64 000000504b7ffd30 00007ffe7ef1776f <span style='color: blue; font-weight: bold'>clr!ThreadpoolMgr::WorkerThreadStart</span>+0xf6, calling clr!ThreadpoolMgr::ExecuteWorkRequest 000000504b7ffd60 00007ffe7ef15809 clr!EEHeapFreeInProcessHeap+0x45, calling KERNEL32!HeapFreeStub 000000504b7ffd90 00007ffe7ef15863 clr!operator delete+0x33 000000504b7ffdd0 00007ffe7ef1b5b5 clr!Thread::intermediateThreadProc+0x8b 000000504b7ffed0 00007ffe7ef1b590 clr!Thread::intermediateThreadProc+0x66, calling clr!_chkstk 000000504b7fff10 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000504b7fff40 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop --------------------------------------------- Thread 10 Current frame: ntdll!NtWaitForSingleObject+0x14 Child-SP RetAddr Caller, Callee 000000504b8ff7b0 00007ffe947926ee KERNELBASE!WaitForSingleObjectEx+0x8e, calling ntdll!NtWaitForSingleObject 000000504b8ff850 00007ffe7ef17df0 clr!CLRSemaphore::Wait+0x7d, calling KERNEL32!WaitForSingleObjectEx 000000504b8ff910 00007ffe7ef1959c clr!ThreadpoolMgr::UnfairSemaphore::Wait+0x115, calling clr!CLRSemaphore::Wait 000000504b8ff960 00007ffe7ef19545 clr!ThreadpoolMgr::WorkerThreadStart+0x2c2, calling clr!ThreadpoolMgr::UnfairSemaphore::Wait 000000504b8ff990 00007ffe7ef15809 clr!EEHeapFreeInProcessHeap+0x45, calling KERNEL32!HeapFreeStub 000000504b8ff9c0 00007ffe7ef15863 clr!operator delete+0x33 000000504b8ffa00 00007ffe7ef1b5b5 clr!Thread::intermediateThreadProc+0x8b 000000504b8ffb80 00007ffe7ef1b590 clr!Thread::intermediateThreadProc+0x66, calling clr!_chkstk 000000504b8ffbc0 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000504b8ffbf0 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop </pre> <br /> 보는 바와 같이 이렇게 정리됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0번: FileStream.BeginRead를 호출하고 WaitOne으로 대기 중인 Main 스레드 5번: Finalizer 스레드 9번: 0번 스레드가 호출했던 FileStream.BeginRead로 인해 FileStream.Read 동기 메서드 호출을 담당하는 스레드 풀의 스레드 10번: 스레드 풀의 여유 스레드 </pre> <br /> 그리고, Read 완료가 된 후 Callback을 실행하는 중 (위의 코드에서는 Thread.Sleep으로 대기일 때) windbg로 다시 스레드를 조사하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:010> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 3 UnstartedThread: 0 BackgroundThread: 2 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 1d88 000001dd15b85a00 2a020 Preemptive 000001DD177919C0:000001DD17791FD0 000001dd15b596b0 1 MTA 5 2 8818 000001dd15baea10 2b220 Preemptive 0000000000000000:0000000000000000 000001dd15b596b0 0 MTA (Finalizer) 9 3 2080 000001dd406596b0 3029220 Preemptive 000001DD17792A38:000001DD17793FD0 000001dd15b596b0 0 MTA (Threadpool Worker) </pre> <br /> 각각의 스레드 호출 스택을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:010> <span style='color: blue; font-weight: bold'>!eestack</span> --------------------------------------------- Thread 0 Current frame: ntdll!NtReadFile+0x14 Child-SP RetAddr Caller, Callee ...[생략]... 000000504aefe930 00007ffe5df98773 (MethodDesc 00007ffe5db7d3a8 +0x163 <span style='color: blue; font-weight: bold'>System.IO.StreamReader.ReadLine</span>()) ...[생략]... 000000504aefea20 00007ffe1f9e0bc2 (MethodDesc 00007ffe1f8d5a00 +0x332 ConsoleApp1.Program.Main(System.String[])), calling (MethodDesc 00007ffe5db54888 +0 System.Console.ReadLine()) ...[생략]... 000000504aeff910 00007ffe808aacd2 MSCOREE!CorExeMain_Exported+0x102, calling KERNEL32!GetProcAddressStub 000000504aeff930 00007ffe808aac42 MSCOREE!CorExeMain_Exported+0x72, calling MSCOREE!guard_dispatch_icall_nop 000000504aeff960 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000504aeff990 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop --------------------------------------------- Thread 5 Current frame: ntdll!NtWaitForMultipleObjects+0x14 Child-SP RetAddr Caller, Callee ...[생략]... 000000504b3ff8f0 00007ffe7ef6f0e3 clr!FinalizerThread::FinalizerThreadWorker+0x53, calling <span style='color: blue; font-weight: bold'>clr!FinalizerThread::WaitForFinalizerEvent</span> ...[생략]... 000000504b3ffc50 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000504b3ffc80 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop --------------------------------------------- Thread 9 Current frame: ntdll!NtDelayExecution+0x14 Child-SP RetAddr Caller, Callee 000000504b7fef40 00007ffe947b818e KERNELBASE!SleepEx+0x9e, calling ntdll!NtDelayExecution ...[생략]... 000000504b7ff1d0 00007ffe5dfc4bdb (MethodDesc 00007ffe5db5e938 +0xb <span style='color: blue; font-weight: bold'>System.Threading.Thread.Sleep</span>(Int32)), calling 00007ffe7ef5ab30 (stub for System.Threading.Thread.SleepInternal(Int32)) ...[생략]... 000000504b7ffd30 00007ffe7ef1776f clr!ThreadpoolMgr::WorkerThreadStart+0xf6, calling clr!ThreadpoolMgr::ExecuteWorkRequest 000000504b7ffd60 00007ffe7ef15809 clr!EEHeapFreeInProcessHeap+0x45, calling KERNEL32!HeapFreeStub 000000504b7ffd90 00007ffe7ef15863 clr!operator delete+0x33 000000504b7ffdd0 00007ffe7ef1b5b5 clr!Thread::intermediateThreadProc+0x8b 000000504b7ffed0 00007ffe7ef1b590 clr!Thread::intermediateThreadProc+0x66, calling clr!_chkstk 000000504b7fff10 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000504b7fff40 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop </pre> <br /> Main 스레드는 Read 완료로 인해 "result.AsyncWaitHandle.WaitOne();"을 지나 Console.ReadLine을 실행 중이고, 이전에 Read 동기 메서드의 호출을 담당하던 스레드 풀의 스레드가 그대로 (Thread.Sleep 코드를 담고 있는) 콜백 메서드를 호출하고 있습니다. 이런 상황을 그림으로 나타내면 이렇게 표현할 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='io_thread_file_dgm_1.png' src='/SysWebRes/bbs/io_thread_file_dgm_1.png' /><br /> <br /> 하지만 언제나 저런 식으로 나오는 것은 아닙니다. 상황에 따라 Callback 실행을 다른 스레드 풀의 스레드에 맡기는 경우도 있으므로 아래와 같은 식으로 흐를 수도 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='io_thread_file_dgm_2.png' src='/SysWebRes/bbs/io_thread_file_dgm_2.png' /><br /> <br /> 여기서 중요한 것은, 어차피 "Read" 메서드를 다른 스레드에서 실행하고 있어야 하므로 1개의 스레드가 점유된다는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 이걸 비동기 모드로 열어 Begin/End 메서드를 호출해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, <span style='color: blue; font-weight: bold'>true</span>); </pre> <br /> 역시 이번에도 BeginRead 호출 후 콜백이 불리기 전 windbg로 조사해 보면 이렇게 스레드가 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 3 UnstartedThread: 0 BackgroundThread: 2 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 5994 000002b120314ca0 202a020 Preemptive 000002B121D81598:000002B121D81FD0 000002b1202e8800 0 MTA 5 2 75c4 000002b12033e520 2b220 Preemptive 0000000000000000:0000000000000000 000002b1202e8800 0 MTA (Finalizer) 6 3 6138 000002b120371e40 8029220 Preemptive 0000000000000000:0000000000000000 000002b1202e8800 0 MTA (<span style='color: blue; font-weight: bold'>Threadpool Completion Port</span>) </pre> <br /> 이전에는 스레드 풀의 스레드가 "Worker" 유형이었는데 이번에는 "Completion Port"라고 나옵니다. 이와 함께 호출 스택을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!eestack</span> --------------------------------------------- Thread 0 Current frame: ntdll!NtWaitForMultipleObjects+0x14 Child-SP RetAddr Caller, Callee ...[생략]... 000000cb0976e9c0 00007ffe5df59c9f (MethodDesc 00007ffe5db78d98 +0x2f <span style='color: blue; font-weight: bold'>System.Threading.WaitHandle.WaitOne</span>(Int32, Boolean)), calling (MethodDesc 00007ffe5db78dc0 +0 System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)) 000000cb0976ea00 00007ffe1f9e0bc6 (MethodDesc 00007ffe1f8d5a00 +0x336 ConsoleApp1.Program.Main(System.String[])) ...[생략]... 000000cb0976f8c0 00007ffe804c8c01 mscoreei!CorExeMain+0x112 000000cb0976f8f0 00007ffe808a1560 MSCOREE!GetShimImpl+0x18, calling MSCOREE!InitShimImpl 000000cb0976f900 00007ffe808aacd2 MSCOREE!CorExeMain_Exported+0x102, calling KERNEL32!GetProcAddressStub 000000cb0976f920 00007ffe808aac42 MSCOREE!CorExeMain_Exported+0x72, calling MSCOREE!guard_dispatch_icall_nop 000000cb0976f950 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000cb0976f980 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop --------------------------------------------- Thread 5 Current frame: ntdll!NtWaitForMultipleObjects+0x14 Child-SP RetAddr Caller, Callee ...[생략]... 000000cb09eff960 00007ffe7f038706 <span style='color: blue; font-weight: bold'>clr!FinalizerThread::FinalizerThreadStart</span>+0x116, calling clr!ManagedThreadBase_DispatchOuter 000000cb09eff9c0 00007ffe7ef15863 clr!operator delete+0x33 000000cb09effa00 00007ffe7ef1b5b5 clr!Thread::intermediateThreadProc+0x8b 000000cb09effa80 00007ffe7ef1b590 clr!Thread::intermediateThreadProc+0x66, calling clr!_chkstk 000000cb09effac0 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000cb09effaf0 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop --------------------------------------------- Thread 6 Current frame: ntdll!NtRemoveIoCompletion+0x14 Child-SP RetAddr Caller, Callee 000000cb09fff880 00007ffe947ce9bf <span style='color: blue; font-weight: bold'>KERNELBASE!GetQueuedCompletionStatus</span>+0x4f, calling ntdll!NtRemoveIoCompletion 000000cb09fff8e0 00007ffe7f09faa6 clr!ThreadpoolMgr::CompletionPortThreadStart+0x215, calling KERNEL32!GetQueuedCompletionStatusStub 000000cb09fff950 00007ffe7ef15863 clr!operator delete+0x33 000000cb09fff990 00007ffe7ef1b5b5 clr!Thread::intermediateThreadProc+0x8b 000000cb09fffa90 00007ffe7ef1b590 clr!Thread::intermediateThreadProc+0x66, calling clr!_chkstk 000000cb09fffad0 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000cb09fffb00 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop </pre> <br /> 0번 Main 스레드는 WaitOne에서 대기 중이고, BeginRead로 인한 호출은 device driver 단에 비동기로 전달되었으므로 그로 인해 점유되는 스레드는 없습니다. 단지, I/O 스레드 하나가 GetQueuedCompletionStatus를 호출해 IOCP를 위한 비동기 이벤트 대기를 하고 있는데 이것은 BeginRead에 점유된 것은 아니고, IOCP와 연결된 갖가지 I/O 이벤트를 접수할 수 있도록 대기하는 것에 불과합니다.<br /> <br /> 그리고, 최종적으로 Read 완료가 되어 콜백 메서드가 실행될 때를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:010> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 3 UnstartedThread: 0 BackgroundThread: 2 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 5994 000002b120314ca0 2a020 Preemptive 000002B121D81A38:000002B121D81FD0 000002b1202e8800 1 MTA 5 2 75c4 000002b12033e520 2b220 Preemptive 0000000000000000:0000000000000000 000002b1202e8800 0 MTA (Finalizer) 6 3 6138 000002b120371e40 a029220 Preemptive 000002B121D82608:000002B121D83FD0 000002b1202e8800 0 MTA (Threadpool Completion Port) </pre> <br /> 콜스택을 통해 I/O 스레드가 콜백 메서드를 실행하는 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:010> <span style='color: blue; font-weight: bold'>!eestack</span> --------------------------------------------- Thread 0 Current frame: ntdll!NtReadFile+0x14 Child-SP RetAddr Caller, Callee ...[생략]... 000000cb0976e910 00007ffe5df98773 (MethodDesc 00007ffe5db7d3a8 +0x163 <span style='color: blue; font-weight: bold'>System.IO.StreamReader.ReadLine</span>()) ...[생략]... 000000cb0976f8c0 00007ffe804c8c01 mscoreei!CorExeMain+0x112 000000cb0976f8f0 00007ffe808a1560 MSCOREE!GetShimImpl+0x18, calling MSCOREE!InitShimImpl 000000cb0976f900 00007ffe808aacd2 MSCOREE!CorExeMain_Exported+0x102, calling KERNEL32!GetProcAddressStub 000000cb0976f920 00007ffe808aac42 MSCOREE!CorExeMain_Exported+0x72, calling MSCOREE!guard_dispatch_icall_nop 000000cb0976f950 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000cb0976f980 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop --------------------------------------------- Thread 5 Current frame: ntdll!NtWaitForMultipleObjects+0x14 Child-SP RetAddr Caller, Callee ...[생략]... 000000cb09eff960 00007ffe7f038706 <span style='color: blue; font-weight: bold'>clr!FinalizerThread::FinalizerThreadStart</span>+0x116, calling clr!ManagedThreadBase_DispatchOuter 000000cb09eff9c0 00007ffe7ef15863 clr!operator delete+0x33 000000cb09effa00 00007ffe7ef1b5b5 clr!Thread::intermediateThreadProc+0x8b 000000cb09effa80 00007ffe7ef1b590 clr!Thread::intermediateThreadProc+0x66, calling clr!_chkstk 000000cb09effac0 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000cb09effaf0 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop --------------------------------------------- Thread 6 Current frame: ntdll!NtDelayExecution+0x14 Child-SP RetAddr Caller, Callee ...[생략]... 000000cb09fff240 00007ffe5dfc4bdb (MethodDesc 00007ffe5db5e938 +0xb <span style='color: blue; font-weight: bold'>System.Threading.Thread.Sleep</span>(Int32)), calling 00007ffe7ef5ab30 (stub for System.Threading.Thread.SleepInternal(Int32)) ...[생략]... 000000cb09fff8e0 00007ffe7f09fbd5 clr!ThreadpoolMgr::CompletionPortThreadStart+0x604 000000cb09fff950 00007ffe7ef15863 clr!operator delete+0x33 000000cb09fff990 00007ffe7ef1b5b5 clr!Thread::intermediateThreadProc+0x8b 000000cb09fffa90 00007ffe7ef1b590 clr!Thread::intermediateThreadProc+0x66, calling clr!_chkstk 000000cb09fffad0 00007ffe96346fd4 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop 000000cb09fffb00 00007ffe96c9cec1 ntdll!RtlUserThreadStart+0x21, calling ntdll!guard_dispatch_icall_nop </pre> <br /> 이것을 도식화해서 표현하면 이렇습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='io_thread_file_dgm_3.png' src='/SysWebRes/bbs/io_thread_file_dgm_3.png' /><br /> <br /> 보는 바와 같이, 아주 이상적인 비동기에 따른 스레드 활용을 보여주고 있으며, 게다가 기존에는 C/C++로 어렵게 작성했던 IOCP를 .NET Framework 세계에 부드럽게 녹여내 고성능 비동기 프로그래밍이 가능하게 만들었습니다. 오~~~ 멋지지 않나요? ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1601&boardid=331301885'>첨부 파일은 이 글의 예제를 포함</a>하며, <a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1602&boardid=331301885'>다이어그램 또한 PPT 원본</a>을 올렸습니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1785
(왼쪽의 숫자를 입력해야 합니다.)