성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
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'>async/await 사용 시 hang 문제가 발생하는 경우 - 두 번째 이야기</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;' > SynchronizationContext와 async/await - 1. What the? ; <a target='tab' href='http://blog.naver.com/vactorman/220371851151'>http://blog.naver.com/vactorman/220371851151</a> SynchronizationContext와 async/await - 2. 얉은 구조 ; <a target='tab' href='http://blog.naver.com/vactorman/220371861151'>http://blog.naver.com/vactorman/220371861151</a> SynchronizationContext와 async/await - 3. Custom SynchronizationContext 구현 ; <a target='tab' href='http://blog.naver.com/vactorman/220371881666'>http://blog.naver.com/vactorman/220371881666</a> SynchronizationContext와 async/await - 4. async/await 궁합 ; <a target='tab' href='http://blog.naver.com/vactorman/220371896727'>http://blog.naver.com/vactorman/220371896727</a> </pre> <br /> 4번째 "<a target='tab' href='http://blog.naver.com/vactorman/220371896727'>SynchronizationContext와 async/await - 4. async/await 궁합 </a>" 글에 보면 제가 쓴 다음의 글도 소개하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > async/await 사용 시 hang 문제가 발생하는 경우 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1541'>http://www.sysnet.pe.kr/2/0/1541</a> </pre> <br /> 그런데, 4번째 글에서 언급하고 있는 내용이 제 글의 내용과 충돌합니다. 제 글에서는 스레드 블록킹 현상의 원인이 "SynchronizationContext"의 "Post" 처리를 하지 않고 "Send" 처리를 했기 때문이라고 했는데 "<a target='tab' href='http://blog.naver.com/vactorman/220371896727'>SynchronizationContext와 async/await - 4. async/await 궁합 </a>" 글에서는 "async/await 사용 시 await 이하 구문은 현재 SynchronizationContext의 Post()로 재진입하여 수행된다"라고 언급합니다.<br /> <br /> 분명히 누구 하나는 틀린 이야기를 하고 있습니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그래서 천천히 검증을 한번 해봤습니다. ^^ 우선, 소스 코드 디버깅을 하기 위해 cmd.exe 창을 띄우고 아래의 글에 따라,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > COMPLUS_ZapDisable - JIT 최적화 코드 생성 제어 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/646'>http://www.sysnet.pe.kr/2/0/646</a> </pre> <br /> 다음과 같이 최적화 옵션을 끄고 개발 환경을 실행했습니다. (F5 디버깅을 위해!)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\Program Files (x86)\Microsoft Visual Studio 12.0>set COMPLUS_ZapDisable=1 C:\Program Files (x86)\Microsoft Visual Studio 12.0>devenv </pre> <br /> 그런 다음 ".NET Reflector"의 소스 코드 디버깅 옵션을 mscorlib.dll, System.dll, System.Core.dll System.Windows.Forms.dll 들에 대해 "Generate PDB"를 적용해 두었습니다.<br /> <br /> 자... 그래서 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/1541'>async/await 사용 시 hang 문제가 발생하는 경우</a>"의 글에 첨부한 예제 프로젝트를 실행시켜 hang이 걸리게 실행한 다음 콜 스택을 통해 실행이 멈춘 지점을 확인해 보았습니다. "this.Text = textTask.Result" 코드의 실행 부분의 콜 스택에 다음의 InternalWait 호출을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [System.Threading.Tasks.Task<TResult>] internal TResult GetResultCore(bool waitCompletionNotification) { if (!base.IsCompleted) { <span style='color: blue; font-weight: bold'>base.InternalWait(-1, new CancellationToken());</span> } if (waitCompletionNotification) { base.NotifyDebuggerOfWaitCompletionIfNecessary(); } if (!base.IsRanToCompletion) { base.ThrowIfExceptional(true); } return this.m_result; } </pre> <br /> InternalWait 함수를 내려가 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [System.Threading.Tasks.Task] [MethodImpl(MethodImplOptions.NoOptimization)] internal bool InternalWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) { TplEtwProvider log = TplEtwProvider.Log; bool flag = log.IsEnabled(); if (flag) { Task internalCurrent = InternalCurrent; log.TaskWaitBegin((internalCurrent != null) ? internalCurrent.m_taskScheduler.Id : TaskScheduler.Default.Id, (internalCurrent != null) ? internalCurrent.Id : 0, this.Id, TplEtwProvider.TaskWaitBehavior.Synchronous); } bool isCompleted = this.IsCompleted; if (!isCompleted) { Debugger.NotifyOfCrossThreadDependency(); if (((millisecondsTimeout == -1) && !cancellationToken.CanBeCanceled) && (this.WrappedTryRunInline() && this.IsCompleted)) { isCompleted = true; } else { <span style='color: blue; font-weight: bold'>isCompleted = this.SpinThenBlockingWait(millisecondsTimeout, cancellationToken);</span> } } if (flag) { Task task2 = InternalCurrent; if (task2 != null) { log.TaskWaitEnd(task2.m_taskScheduler.Id, task2.Id, this.Id); return isCompleted; } log.TaskWaitEnd(TaskScheduler.Default.Id, 0, this.Id); } return isCompleted; } </pre> <br /> this.SpinThenBlockingWait 호출로 이어지고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [System.Threading.Tasks.Task] private bool SpinThenBlockingWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) { bool flag = millisecondsTimeout == -1; uint num = flag ? 0 : ((uint) Environment.TickCount); bool flag2 = this.SpinWait(millisecondsTimeout); if (!flag2) { <span style='color: blue; font-weight: bold'>SetOnInvokeMres action = new SetOnInvokeMres();</span> try { <span style='color: blue; font-weight: bold'>this.AddCompletionAction(action, true);</span> if (flag) { <span style='color: blue; font-weight: bold'>return action.Wait(-1, cancellationToken);</span> } uint num2 = ((uint) Environment.TickCount) - num; if (num2 < millisecondsTimeout) { flag2 = action.Wait((int) (millisecondsTimeout - ((int) num2)), cancellationToken); } } finally { if (!this.IsCompleted) { this.RemoveContinuation(action); } } } return flag2; } </pre> <br /> 보는 바와 같이 SpinThenBlockingWait 내에서는 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13572#SetOnInvokeMres'>SetOnInvokeMres 타입의 인스턴스</a>를 생성한 후 AddCompletionAction을 호출 한후 action.Wait 대기 상태로 빠집니다. 그럼, SetOnInvokeMres 타입과 AddCompletionAction으로 들어가 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [System.Threading.Tasks.Task] private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction { internal SetOnInvokeMres() : base(false, 0) { } public void Invoke(Task completingTask) { <span style='color: blue; font-weight: bold'>base.Set();</span> } } private void AddCompletionAction(ITaskCompletionAction action, bool addBeforeOthers) // addBeforeOthers == true { if (!<span style='color: blue; font-weight: bold'>this.AddTaskContinuation(action, addBeforeOthers)</span>) // AddTaskContinuation 호출이 return == true 반환해서 메서드를 빠져나감. { action.Invoke(this); // 호출되지 않았음. } } </pre> <br /> 만약 위의 코드에서 AddTaskContinuation 메서드가 false를 반환했다면 action.Invoke가 불렸을 테고, SetOnInvokeMres.Invoke 메서드에서는 "base.Set()"의 호출로 이벤트를 Signal하기 때문에 이후의 action.Wait 대기가 곧바로 해제되어 hang 현상은 없었을 것입니다. 그럼, 다시 AddTaskContinuation을 들어가 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [System.Threading.Tasks.Task] private bool AddTaskContinuation(object tc, bool addBeforeOthers) { if (this.IsCompleted) { return false; } return (<span style='color: blue; font-weight: bold'>((this.m_continuationObject == null) && (Interlocked.CompareExchange(ref this.m_continuationObject, tc, null) == null))</span> || this.AddTaskContinuationComplex(tc, addBeforeOthers)); } </pre> <br /> hang 현상이 발생했을 때의 코드 실행은 this.m_continuationObject == null인 상태로, Interlocked.CompareExchange 코드에 의해 tc (즉, SetOnInvokeMres 인스턴스) 객체를 this.m_continuationObject 에 할당하는 것으로 마무리를 지은 후 true를 반환합니다.<br /> <br /> 최종적으로는 결국, this.m_continuationObject 객체의 Invoke 메서드가 호출되지 않았기 때문에 발생하는 것입니다. 그렇다면 해당 객체의 Invoke 메서드는 언제 발생하는 것일까요? 추적해 보면 특별한 예외 상황이 발생하지 않는 경우를 제외한다면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [System.Threading.Tasks.Task] [SecuritySafeCritical] internal void FinishContinuations() { object obj2 = Interlocked.Exchange(ref this.m_continuationObject, s_taskCompletionSentinel); TplEtwProvider.Log.RunningContinuation(this.Id, obj2); if (obj2 != null) { if (AsyncCausalityTracer.LoggingOn) { AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, this.Id, CausalitySynchronousWork.CompletionNotification); } bool allowInlining = (((this.m_stateFlags & 0x8000000) == 0) && (Thread.CurrentThread.ThreadState != ThreadState.AbortRequested)) && ((this.m_stateFlags & 0x40) == 0); Action action = obj2 as Action; if (action != null) { AwaitTaskContinuation.RunOrScheduleAction(action, allowInlining, ref t_currentTask); this.LogFinishCompletionNotification(); } else { ITaskCompletionAction action2 = obj2 as ITaskCompletionAction; if (action2 != null) { <span style='color: blue; font-weight: bold'>action2.Invoke(this);</span> this.LogFinishCompletionNotification(); } else { // ...[생략]... } } } } </pre> <br /> FinishContinuations 메서드가 나오고, 그것을 호출하는 메서드들은 FinishStageThree, FinishStageTwo를 이어 Finish 메서드까지 이어갑니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [System.Threading.Tasks.Task] internal void FinishStageThree() { this.m_action = null; if (((this.m_parent != null) && ((this.m_parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == TaskCreationOptions.None)) && (((this.m_stateFlags & 0xffff) & 4) != 0)) { this.m_parent.ProcessChildCompletion(this); } <span style='color: blue; font-weight: bold'>this.FinishContinuations();</span> } internal void FinishStageTwo() { int num; this.AddExceptionsFromChildren(); // ...[생략]... Interlocked.Exchange(ref this.m_stateFlags, this.m_stateFlags | num); ContingentProperties contingentProperties = this.m_contingentProperties; if (contingentProperties != null) { contingentProperties.SetCompleted(); contingentProperties.DeregisterCancellationCallback(); } <span style='color: blue; font-weight: bold'>this.FinishStageThree();</span> } internal void Finish(bool bUserDelegateExecuted) { if (!bUserDelegateExecuted) { <span style='color: blue; font-weight: bold'>this.FinishStageTwo();</span> } else { ContingentProperties contingentProperties = this.m_contingentProperties; if (((contingentProperties == null) || ((contingentProperties.m_completionCountdown == 1) && !this.IsSelfReplicatingRoot)) || (Interlocked.Decrement(ref contingentProperties.m_completionCountdown) == 0)) { <span style='color: blue; font-weight: bold'>this.FinishStageTwo();</span> } else { this.AtomicStateUpdate(0x800000, 0x1600000); } // ...[생략]... } } </pre> <br /> 마지막 최상위 호출은 ExecuteWithThreadLocal에서 이뤄집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [System.Threading.Tasks.Task] [SecurityCritical] private void ExecuteWithThreadLocal(ref Task currentTaskSlot) { Task task = currentTaskSlot; TplEtwProvider log = TplEtwProvider.Log; Guid oldActivityThatWillContinue = new Guid(); bool flag = log.IsEnabled(); if (flag) { if (log.TasksSetActivityIds) { EventSource.SetCurrentThreadActivityId(TplEtwProvider.CreateGuidForTaskID(this.Id), out oldActivityThatWillContinue); } if (task != null) { log.TaskStarted(task.m_taskScheduler.Id, task.Id, this.Id); } else { log.TaskStarted(TaskScheduler.Current.Id, 0, this.Id); } } if (AsyncCausalityTracer.LoggingOn) { AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, this.Id, CausalitySynchronousWork.Execution); } try { currentTaskSlot = this; ExecutionContext capturedContext = this.CapturedContext; if (capturedContext == null) { this.Execute(); } else { if (this.IsSelfReplicatingRoot || this.IsChildReplica) { this.CapturedContext = CopyExecutionContext(capturedContext); } ContextCallback callback = s_ecCallback; if (callback == null) { s_ecCallback = callback = new ContextCallback(Task.ExecutionContextCallback); } ExecutionContext.Run(capturedContext, callback, this, true); } if (AsyncCausalityTracer.LoggingOn) { AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution); } <span style='color: blue; font-weight: bold'>this.Finish(true);</span> } finally { currentTaskSlot = task; if (flag) { if (task != null) { log.TaskCompleted(task.m_taskScheduler.Id, task.Id, this.Id, this.IsFaulted); } else { log.TaskCompleted(TaskScheduler.Current.Id, 0, this.Id, this.IsFaulted); } if (log.TasksSetActivityIds) { EventSource.SetCurrentThreadActivityId(oldActivityThatWillContinue); } } } } </pre> <br /> ExecuteWithThreadLocal이 실행될 때의 callstack을 보면, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) Line 1003 mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) Line 929 mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() Line 2217 mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() Line 119 mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() Line 12 </pre> <br /> _ThreadPoolWaitCallback.PerformWaitCallback이 루트인데, 결국 ThreadPool의 작업자 스레드의 하나로 실행된 것입니다. 이 스레드는 await로 인해 최초 비동기 호출을 했을 때의 바로 그 스레드입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public async Task<string> GetHtmlTextAsync() { string result = <span style='color: blue; font-weight: bold'>await GetTextAsync();</span> return result; } </pre> <br /> 여기까지 정리한 것을 우리가 만들었던 C# 소스코드에 적용해 설명해 보면, 대충 이렇습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private void Form1_Load(object sender, EventArgs e) { var textTask = GetHtmlTextAsync(); // testTask는 Task 인스턴스이고, // testTask 인스턴스에 할당된 스레드는 _ThreadPoolWaitCallback.PerformWaitCallback ~ ExecuteWithThreadLocal 호출의 작업자 스레드임 this.Text = textTask.Result; // 이곳에서 action.Wait의 호출로 Task 작업자 스레드가 모든 작업이 완료될때까지 대기. } public async Task<string> GetHtmlTextAsync() { string result = await GetTextAsync(); // _ThreadPoolWaitCallback.PerformWaitCallback 작업자 스레드에 의해 // Task.Factory.StartNew에 전달된 익명 함수를 실행하고, // 나머지 다음의 작업을, // string result = ... // return result; // _ThreadPoolWaitCallback.PerformWaitCallback을 실행하던 작업자 스레드가 마저 실행해야 함. // 그런데, 윈도우 폼 응용 프로그램에는 미리 설정된 SynchronizationContext가 있으므로, // _ThreadPoolWaitCallback.PerformWaitCallback을 실행하던 작업자 스레드가 나머지 작업을 직접 실행하지 못하고, // WindowsFormsSynchronizationContext.Post로 전달하고, // 이 단계에서 Task의 작업자 스레드는 모든 처리를 완료했으므로 스레드 풀에 반환됨. return result; } public Task<string> GetTextAsync() { return Task.Factory.StartNew( () => { Thread.Sleep(1000); return "Hello World"; }); } </pre> <br /> 자, 여기가 문제입니다. 저는 WindowsFormsSynchronizationContext의 Post가 아닌 Send로 했기 때문에 블록킹이 발생했을 거라고 예상했는데요. 반면, "<a target='tab' href='http://blog.naver.com/vactorman/220371896727'>SynchronizationContext와 async/await - 4. async/await 궁합</a>" 글에서는 await은 Post로 전달한다고 쓰여 있는 것입니다. 확인은 WindowsFormsSynchronizationContext.Post 메서드에 BP를 거는 것으로 금방 알 수 있습니다. 그리고, 결과는 실제로 ^^; Send가 아닌 Post가 실행되었습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='synch_context_1.png' src='/SysWebRes/bbs/synch_context_1.png' /><br /> <br /> 그렇습니다. Send가 아닌 Post가 맞습니다.<br /> <br /> <hr style='width: 50%' /><br /> <a name='hang'></a> <br /> 아니, 그럼 아래의 글에서 설명한 hang 현상의 원인은 도대체 무엇입니까?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > async/await 사용 시 hang 문제가 발생하는 경우 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1541'>http://www.sysnet.pe.kr/2/0/1541</a> </pre> <br /> 결론먼저 말하면 이 원인은, Send/Post의 문제가 아니고 어찌되었든 "this.Text = textTask.Result;" 코드의 호출로 인해 UI 스레드가 blocking이 되면서 WindowsFormsSynchronizationContext에서 Post로 전달해 준 await 이후의 분리된 코드를 호출하지 못해서 발생하는 것입니다. 이는 WindowsFormsSynchronizationContext.Post의 호출에 전달된 SendOrPostCallback 델리게이트 타입인 d 인자의 값을 보면 어느 정도 추측할 수 있습니다. 위의 그림에서 보면 "d.Target" 인자의 값이 "System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation" 임을 알 수 있는데, 이때의 코드가 바로 await 이후로 분리된 코드인 것입니다. (정확히는, 분리된 코드는 MoveNext의 switch 코드의 하나이고, 그 MoveNext를 호출하고 Task.Finish를 호출해줄 코드가 담긴 "Void <_cctor>b__3(System.Object)" 메서드입니다.)<br /> <br /> 문제의 시나리오를 정리해 보면 이렇게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 1. UI Thread가 Form1_Load를 실행 1.1. UI Thread가 GetHtmlTextAsync 메서드를 실행 1.1.1 UI Thread가 TestClass.GetTextAsync 메서드를 비동기로 실행 1.2. UI Thread는 TestClass.GetTextAsync 메서드에 반환된 Task 인스턴스인 textTask의 get_Result 프로퍼티를 호출. 내부적으로 Task 타입의 Result는 해당 Task가 담당한 모든 작업이 끝날 때 까지 대기 여기서 Task가 담당한 작업은 == await 대상 코드 + await 이후의 코드 2. ThreadPool의 자유 스레드가 TestClass.GetTextAsync 메서드에서 반환한 Task에 할당됨. 2.1. Task.Factory.StartNew에 넘겨진 익명 메서드를 실행 2.2. 메서드 실행 후 결과 값 반환 2.3. await 처리로 인해 분리된 "return result;" 코드를 SynchronizationContextAwaitTaskContinuation 델리게이트로 WindowsFormsSynchronizationContext.Post에 전달 2.4 전달된 SynchronizationContextAwaitTaskContinuation 델리게이트 코드까지 실행되어야만 Task의 모든 작업이 완료되는데, UI Thread는 현재 Task 타입의 Result 호출로 인해 블록 상태이므로, WindowsFormsSynchronizationContext가 전달한 델리게이트를 실행하지 못함. </pre> <br /> 휴~~~ 이렇게 정리하고 나니, 속이 시원하군요. ^^ 암튼, "<a target='tab' href='http://blog.naver.com/vactorman/220371896727'>SynchronizationContext와 async/await - 4. async/await 궁합 </a>" 글 덕분에 문제를 좀 더 바로 보게 되어 다행입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
7691
(왼쪽의 숫자를 입력해야 합니다.)