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를 이용하는 방법을 소개하고 있습니다.
정성태

... 31  32  33  34  35  36  37  38  39  40  41  [42]  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12596정성태4/12/20219697개발 환경 구성: 568. Windows의 80 포트 점유를 해제하는 방법
12595정성태4/12/20219087.NET Framework: 1036. SQL 서버 - varbinary 타입에 대한 문자열의 CAST, CONVERT 변환을 C# 코드로 구현
12594정성태4/11/20218519.NET Framework: 1035. C# - kubectl 명령어 또는 REST API 대신 Kubernetes 클라이언트 라이브러리를 통해 프로그래밍으로 접근 [1]파일 다운로드1
12593정성태4/10/20219725개발 환경 구성: 567. Docker Desktop for Windows - kubectl proxy 없이 k8s 대시보드 접근 방법
12592정성태4/10/20219582개발 환경 구성: 566. Docker Desktop for Windows - k8s dashboard의 Kubeconfig 로그인 및 Skip 방법
12591정성태4/9/202112862.NET Framework: 1034. C# - byte 배열을 Hex(16진수) 문자열로 고속 변환하는 방법 [2]파일 다운로드1
12590정성태4/9/20219343.NET Framework: 1033. C# - .NET 4.0 이하에서 Console.IsInputRedirected 구현 [1]
12589정성태4/8/202110709.NET Framework: 1032. C# - Environment.OSVersion의 문제점 및 윈도우 운영체제의 버전을 구하는 다양한 방법 [1]
12588정성태4/7/202111211개발 환경 구성: 565. PowerShell - New-SelfSignedCertificate를 사용해 CA 인증서 생성 및 인증서 서명 방법
12587정성태4/6/202112084개발 환경 구성: 564. Windows 10 - ClickOnce 배포처럼 사용할 수 있는 MSIX 설치 파일 [1]
12586정성태4/5/20219746오류 유형: 710. Windows - Restart-Computer / shutdown 명령어 수행 시 Access is denied(E_ACCESSDENIED)
12585정성태4/5/20219441개발 환경 구성: 563. 기본 생성된 kubeconfig 파일의 내용을 새롭게 생성한 인증서로 구성하는 방법
12584정성태4/1/202110158개발 환경 구성: 562. kubeconfig 파일 없이 kubectl 옵션만으로 실행하는 방법
12583정성태3/29/202111643개발 환경 구성: 561. kubectl 수행 시 다른 k8s 클러스터로 접속하는 방법
12582정성태3/29/202110355오류 유형: 709. Visual C++ - 컴파일 에러 error C2059: syntax error: '__stdcall'
12581정성태3/28/202110279.NET Framework: 1031. WinForm/WPF에서 Console 창을 띄워 출력하는 방법 (2) - Output 디버깅 출력을 AllocConsole로 우회 [2]
12580정성태3/28/20218961오류 유형: 708. SQL Server Management Studio - Execution Timeout Expired.
12579정성태3/28/20219109오류 유형: 707. 중첩 가상화(Nested Virtualization) - The virtual machine could not be started because this platform does not support nested virtualization.
12578정성태3/27/20219335개발 환경 구성: 560. Docker Desktop for Windows 기반의 Kubernetes 구성 (2) - WSL 2 인스턴스에 kind가 구성한 k8s 서비스 위치
12577정성태3/26/202111380개발 환경 구성: 559. Docker Desktop for Windows 기반의 Kubernetes 구성 - WSL 2 인스턴스에 kind 도구로 k8s 클러스터 구성
12576정성태3/25/20219195개발 환경 구성: 558. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성 (2) - k8s 서비스 위치
12575정성태3/24/20218227개발 환경 구성: 557. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성
12574정성태3/23/202112324.NET Framework: 1030. C# Socket의 Close/Shutdown 동작 (동기 모드)
12573정성태3/22/202110132개발 환경 구성: 556. WSL 인스턴스 초기 설정 명령어 [1]
12572정성태3/22/20219655.NET Framework: 1029. C# - GC 호출로 인한 메모리 압축(Compaction)을 확인하는 방법파일 다운로드1
12571정성태3/21/20218823오류 유형: 706. WSL 2 기반으로 "Enable Kubernetes" 활성화 시 초기화 실패 [1]
... 31  32  33  34  35  36  37  38  39  40  41  [42]  43  44  45  ...