Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)
(시리즈 글이 7개 있습니다.)
.NET Framework: 394. async/await 사용 시 hang 문제가 발생하는 경우
; https://www.sysnet.pe.kr/2/0/1541

.NET Framework: 512. async/await 사용 시 hang 문제가 발생하는 경우 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/10801

.NET Framework: 631. async/await에 대한 "There Is No Thread" 글의 부가 설명
; https://www.sysnet.pe.kr/2/0/11129

.NET Framework: 720. 비동기 메서드 내에서 await 시 ConfigureAwait 호출 의미
; https://www.sysnet.pe.kr/2/0/11418

.NET Framework: 721. WebClient 타입의 ...Async 메서드 호출은 왜 await + 동기 호출 시 hang 현상이 발생할까요?
; https://www.sysnet.pe.kr/2/0/11419

디버깅 기술: 196. windbg - async/await 비동기인 경우 메모리 덤프 분석의 어려움
; https://www.sysnet.pe.kr/2/0/13563

닷넷: 2225. Windbg - dumasync로 분석하는 async/await 호출
; https://www.sysnet.pe.kr/2/0/13573




async/await 사용 시 hang 문제가 발생하는 경우 - 두 번째 이야기

오호~~~ 재미있는 글이 쓰여졌군요. ^^

SynchronizationContext와 async/await - 1. What the?
; http://blog.naver.com/vactorman/220371851151

SynchronizationContext와 async/await - 2. 얉은 구조
; http://blog.naver.com/vactorman/220371861151

SynchronizationContext와 async/await - 3. Custom SynchronizationContext 구현
; http://blog.naver.com/vactorman/220371881666

SynchronizationContext와 async/await - 4. async/await 궁합 
; http://blog.naver.com/vactorman/220371896727

4번째 "SynchronizationContext와 async/await - 4. async/await 궁합 " 글에 보면 제가 쓴 다음의 글도 소개하고 있습니다.

async/await 사용 시 hang 문제가 발생하는 경우
; https://www.sysnet.pe.kr/2/0/1541

그런데, 4번째 글에서 언급하고 있는 내용이 제 글의 내용과 충돌합니다. 제 글에서는 스레드 블록킹 현상의 원인이 "SynchronizationContext"의 "Post" 처리를 하지 않고 "Send" 처리를 했기 때문이라고 했는데 "SynchronizationContext와 async/await - 4. async/await 궁합 " 글에서는 "async/await 사용 시 await 이하 구문은 현재 SynchronizationContext의 Post()로 재진입하여 수행된다"라고 언급합니다.

분명히 누구 하나는 틀린 이야기를 하고 있습니다. ^^




그래서 천천히 검증을 한번 해봤습니다. ^^ 우선, 소스 코드 디버깅을 하기 위해 cmd.exe 창을 띄우고 아래의 글에 따라,

COMPLUS_ZapDisable - JIT 최적화 코드 생성 제어
; https://www.sysnet.pe.kr/2/0/646

다음과 같이 최적화 옵션을 끄고 개발 환경을 실행했습니다. (F5 디버깅을 위해!)

C:\Program Files (x86)\Microsoft Visual Studio 12.0>set COMPLUS_ZapDisable=1
C:\Program Files (x86)\Microsoft Visual Studio 12.0>devenv

그런 다음 ".NET Reflector"의 소스 코드 디버깅 옵션을 mscorlib.dll, System.dll, System.Core.dll System.Windows.Forms.dll 들에 대해 "Generate PDB"를 적용해 두었습니다.

자... 그래서 "async/await 사용 시 hang 문제가 발생하는 경우"의 글에 첨부한 예제 프로젝트를 실행시켜 hang이 걸리게 실행한 다음 콜 스택을 통해 실행이 멈춘 지점을 확인해 보았습니다. "this.Text = textTask.Result" 코드의 실행 부분의 콜 스택에 다음의 InternalWait 호출을 확인할 수 있습니다.

[System.Threading.Tasks.Task<TResult>]

        internal TResult GetResultCore(bool waitCompletionNotification)
        {
            if (!base.IsCompleted)
            {
                base.InternalWait(-1, new CancellationToken());
            }
            if (waitCompletionNotification)
            {
                base.NotifyDebuggerOfWaitCompletionIfNecessary();
            }
            if (!base.IsRanToCompletion)
            {
                base.ThrowIfExceptional(true);
            }
            return this.m_result;
        }

InternalWait 함수를 내려가 보면,

[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
                {
                    isCompleted = this.SpinThenBlockingWait(millisecondsTimeout, cancellationToken);
                }
            }
            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;
        }

this.SpinThenBlockingWait 호출로 이어지고,

[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)
            {
                SetOnInvokeMres action = new SetOnInvokeMres();
                try
                {
                    this.AddCompletionAction(action, true);
                    if (flag)
                    {
                        return action.Wait(-1, cancellationToken);
                    }
                    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;
        }

보는 바와 같이 SpinThenBlockingWait 내에서는 SetOnInvokeMres 타입의 인스턴스를 생성한 후 AddCompletionAction을 호출 한후 action.Wait 대기 상태로 빠집니다. 그럼, SetOnInvokeMres 타입과 AddCompletionAction으로 들어가 볼까요?

[System.Threading.Tasks.Task]

        private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction
        {
            internal SetOnInvokeMres() : base(false, 0)
            {
            }
            
            public void Invoke(Task completingTask)
            {
                base.Set();
            }
        }

        private void AddCompletionAction(ITaskCompletionAction action, bool addBeforeOthers) // addBeforeOthers == true
        {
            if (!this.AddTaskContinuation(action, addBeforeOthers)) // AddTaskContinuation 호출이 return == true 반환해서 메서드를 빠져나감.
            {
                action.Invoke(this); // 호출되지 않았음.
            }
        }

만약 위의 코드에서 AddTaskContinuation 메서드가 false를 반환했다면 action.Invoke가 불렸을 테고, SetOnInvokeMres.Invoke 메서드에서는 "base.Set()"의 호출로 이벤트를 Signal하기 때문에 이후의 action.Wait 대기가 곧바로 해제되어 hang 현상은 없었을 것입니다. 그럼, 다시 AddTaskContinuation을 들어가 보면,

[System.Threading.Tasks.Task]

        private bool AddTaskContinuation(object tc, bool addBeforeOthers)
        {
            if (this.IsCompleted)
            {
                return false;
            }
            return (((this.m_continuationObject == null) && (Interlocked.CompareExchange(ref this.m_continuationObject, tc, null) == null)) || this.AddTaskContinuationComplex(tc, addBeforeOthers));
        }

hang 현상이 발생했을 때의 코드 실행은 this.m_continuationObject == null인 상태로, Interlocked.CompareExchange 코드에 의해 tc (즉, SetOnInvokeMres 인스턴스) 객체를 this.m_continuationObject 에 할당하는 것으로 마무리를 지은 후 true를 반환합니다.

최종적으로는 결국, this.m_continuationObject 객체의 Invoke 메서드가 호출되지 않았기 때문에 발생하는 것입니다. 그렇다면 해당 객체의 Invoke 메서드는 언제 발생하는 것일까요? 추적해 보면 특별한 예외 상황이 발생하지 않는 경우를 제외한다면,

[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)
                    {
                        action2.Invoke(this);
                        this.LogFinishCompletionNotification();
                    }
                    else
                    {
                        // ...[생략]...
                    }
                }
            }
        }

FinishContinuations 메서드가 나오고, 그것을 호출하는 메서드들은 FinishStageThree, FinishStageTwo를 이어 Finish 메서드까지 이어갑니다.

[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);
            }
            this.FinishContinuations();
        }
        
        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();
            }
            this.FinishStageThree();
        }
        
        internal void Finish(bool bUserDelegateExecuted)
        {
            if (!bUserDelegateExecuted)
            {
                this.FinishStageTwo();
            }
            else
            {
                ContingentProperties contingentProperties = this.m_contingentProperties;
                if (((contingentProperties == null) || ((contingentProperties.m_completionCountdown == 1) && !this.IsSelfReplicatingRoot)) || (Interlocked.Decrement(ref contingentProperties.m_completionCountdown) == 0))
                {
                    this.FinishStageTwo();
                }
                else
                {
                    this.AtomicStateUpdate(0x800000, 0x1600000);
                }
                // ...[생략]...
            }
        }

마지막 최상위 호출은 ExecuteWithThreadLocal에서 이뤄집니다.

[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);
                }
                this.Finish(true);
            }
            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);
                    }
                }
            }
        }

ExecuteWithThreadLocal이 실행될 때의 callstack을 보면,

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  

_ThreadPoolWaitCallback.PerformWaitCallback이 루트인데, 결국 ThreadPool의 작업자 스레드의 하나로 실행된 것입니다. 이 스레드는 await로 인해 최초 비동기 호출을 했을 때의 바로 그 스레드입니다.

public async Task<string> GetHtmlTextAsync()
{
    string result = await GetTextAsync();
    return result;
}

여기까지 정리한 것을 우리가 만들었던 C# 소스코드에 적용해 설명해 보면, 대충 이렇습니다.

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";
    });
}

자, 여기가 문제입니다. 저는 WindowsFormsSynchronizationContext의 Post가 아닌 Send로 했기 때문에 블록킹이 발생했을 거라고 예상했는데요. 반면, "SynchronizationContext와 async/await - 4. async/await 궁합" 글에서는 await은 Post로 전달한다고 쓰여 있는 것입니다. 확인은 WindowsFormsSynchronizationContext.Post 메서드에 BP를 거는 것으로 금방 알 수 있습니다. 그리고, 결과는 실제로 ^^; Send가 아닌 Post가 실행되었습니다.

synch_context_1.png

그렇습니다. Send가 아닌 Post가 맞습니다.




아니, 그럼 아래의 글에서 설명한 hang 현상의 원인은 도대체 무엇입니까?

async/await 사용 시 hang 문제가 발생하는 경우
; https://www.sysnet.pe.kr/2/0/1541

결론먼저 말하면 이 원인은, 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)" 메서드입니다.)

문제의 시나리오를 정리해 보면 이렇게 됩니다.

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가 전달한 델리게이트를 실행하지 못함.

휴~~~ 이렇게 정리하고 나니, 속이 시원하군요. ^^ 암튼, "SynchronizationContext와 async/await - 4. async/await 궁합 " 글 덕분에 문제를 좀 더 바로 보게 되어 다행입니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 3/5/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2015-06-01 04시55분
멋진 정리 감사합니다. async/await 의 사용에 이런 문제가 있었던지는 몰랐습니다.
모르는 상태에서 처음 보고 당황하게 될 많은 이들을(저 포함) 구제해주는 글이 아닐까 싶습니다.
Beren Ko
2015-06-01 03시24분
결국, 이 모든 문제의 근원은... 'UI를 만든 스레드만이 UI를 접근해야 한다'라서, 이런 속내를 알지 못하면 그야말로 황당한 hang 현상일 수 있습니다. (아마 모르긴 해도... 마이크로소프트도 이 기능을 넣으면서 꽤나 고민했을 듯합니다. ^^)
정성태
2024-05-02 09시06분
Detecting blocking calls using async in C#
; https://www.jamescrosswell.dev/posts/async-detecting-blocking-calls
; https://github.com/benaadams/Ben.BlockingDetector/blob/main/src/Ben.BlockingDetector/TaskBlockingListener.cs

SynchronizationContext를 사용하던 ASP.NET (.NET Framework) 시절까지만 해도 DetectBlockingSynchronizationContext를 교체해 넣어서 blocking 호출을 잡아낼 수 있었지만, 그것이 없는 ASP.NET Core부터는 그것을 감지하기 위해 EventListener를 활용한 TaskBlockingListener를 이용하는 방법을 소개하고 있습니다.
정성태

... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...
NoWriterDateCnt.TitleFile(s)
1303정성태6/26/201227409개발 환경 구성: 152. sysnet DB를 SQL Azure 데이터베이스로 마이그레이션
1302정성태6/25/201229480개발 환경 구성: 151. Azure 웹 사이트에 사용자 도메인 네임 연결하는 방법
1301정성태6/20/201225767오류 유형: 156. KB2667402 윈도우 업데이트 실패 및 마이크로소프트 Answers 웹 사이트 대응
1300정성태6/20/201231796.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 [1]파일 다운로드2
1299정성태6/18/201232904제니퍼 .NET: 21. 제니퍼 닷넷 - Ninject DI 프레임워크의 성능 분석 [2]파일 다운로드2
1298정성태6/14/201234421VS.NET IDE: 72. Visual Studio에서 pfx 파일로 서명한 경우, 암호는 어디에 저장될까? [2]
1297정성태6/12/201231061VC++: 63. 다른 프로세스에 환경 변수 설정하는 방법파일 다운로드1
1296정성태6/5/201227710.NET Framework: 328. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기 [4]파일 다운로드1
1295정성태6/5/201225092.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기파일 다운로드1
1294정성태5/27/201248554.NET Framework: 326. 유니코드와 한글 - 유니코드와 닷넷을 이용한 한글 처리 [7]파일 다운로드2
1293정성태5/24/201229782.NET Framework: 325. System.Drawing.Bitmap 데이터를 Parallel.For로 처리하는 방법 [2]파일 다운로드1
1292정성태5/24/201223765.NET Framework: 324. First-chance exception에 대해 조건에 따라 디버거가 멈추게 할 수는 없을까? [1]파일 다운로드1
1291정성태5/23/201230292VC++: 62. 배열 초기화를 위한 기계어 코드 확인 [2]
1290정성태5/18/201235087.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [7]파일 다운로드1
1289정성태5/17/201239247.NET Framework: 322. regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항 [5]파일 다운로드2
1288정성태5/17/201226470.NET Framework: 321. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library파일 다운로드1
1287정성태5/17/201229307.NET Framework: 320. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0 [2]
1286정성태5/17/201238247.NET Framework: 319. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU [5]
1285정성태5/16/201233277.NET Framework: 318. gacutil.exe로 어셈블리 등록 시 시스템 변경 사항파일 다운로드1
1284정성태5/15/201225710오류 유형: 155. Windows Phone 연결 상태에서 DRIVER POWER STATE FAILURE 블루 스크린 뜨는 현상
1283정성태5/12/201233325.NET Framework: 317. C# 관점에서의 Observer 패턴 구현 [1]파일 다운로드1
1282정성태5/12/201226115Phone: 6. Windows Phone 7 Silverlight에서 Google Map 사용하는 방법 [3]파일 다운로드1
1281정성태5/9/201233202.NET Framework: 316. WPF/Silverlight의 그래픽 단위와 Anti-aliasing 처리를 이해하자 [1]파일 다운로드1
1280정성태5/9/201226165오류 유형: 154. Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'System.ServiceModel, ...'.
1279정성태5/9/201224924.NET Framework: 315. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 [1]파일 다운로드1
1278정성태5/8/201226151오류 유형: 153. Visual Studio 디버깅 - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...