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

1  [2]  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13893정성태2/27/20252211Linux: 115. eBPF (bpf2go) - ARRAY / HASH map 기본 사용법
13892정성태2/24/20252963닷넷: 2325. C# - PowerShell과 연동하는 방법파일 다운로드1
13891정성태2/23/20252490닷넷: 2324. C# - 프로세스의 성능 카운터용 인스턴스 이름을 구하는 방법파일 다운로드1
13890정성태2/21/20252316닷넷: 2323. C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(Win32 API)파일 다운로드1
13889정성태2/20/20253038닷넷: 2322. C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(성능 카운터, WMI) [1]파일 다운로드1
13888정성태2/17/20252483닷넷: 2321. Blazor에서 발생할 수 있는 async void 메서드의 부작용
13887정성태2/17/20253063닷넷: 2320. Blazor의 razor 페이지에서 code-behind 파일로 코드를 분리 및 DI 사용법
13886정성태2/15/20252572VS.NET IDE: 196. Visual Studio - Code-behind처럼 cs 파일을 그룹핑하는 방법
13885정성태2/14/20253230닷넷: 2319. ASP.NET Core Web API / Razor 페이지에서 발생할 수 있는 async void 메서드의 부작용
13884정성태2/13/20253503닷넷: 2318. C# - (async Task가 아닌) async void 사용 시의 부작용파일 다운로드1
13883정성태2/12/20253255닷넷: 2317. C# - Memory Mapped I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13882정성태2/10/20252577스크립트: 70. 파이썬 - oracledb 패키지 연동 시 Thin / Thick 모드
13881정성태2/7/20252823닷넷: 2316. C# - Port I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13880정성태2/5/20253167오류 유형: 947. sshd - Failed to start OpenSSH server daemon.
13879정성태2/5/20253389오류 유형: 946. Ubuntu - N: Updating from such a repository can't be done securely, and is therefore disabled by default.
13878정성태2/3/20253182오류 유형: 945. Windows - 최대 절전 모드 시 DRIVER_POWER_STATE_FAILURE 발생 (pacer.sys)
13877정성태1/25/20253236닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI)파일 다운로드1
13876정성태1/25/20253691닷넷: 2314. C# - ProcessStartInfo 타입의 Arguments와 ArgumentList파일 다운로드1
13875정성태1/24/20253124스크립트: 69. 파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers
13874정성태1/24/20253542스크립트: 68. 파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
13873정성태1/23/20252969디버깅 기술: 217. WinDbg - PCI 장치 열거파일 다운로드1
13872정성태1/23/20252880오류 유형: 944. WinDbg - 원격 커널 디버깅이 연결은 되지만 Break (Ctrl + Break) 키를 눌러도 멈추지 않는 현상
13871정성태1/22/20253291Windows: 278. Windows - 윈도우를 다른 모니터 화면으로 이동시키는 단축키 (Window + Shift + 화살표)
13870정성태1/18/20253730개발 환경 구성: 741. WinDbg - 네트워크 커널 디버깅이 가능한 NIC 카드 지원 확대
13869정성태1/18/20253452개발 환경 구성: 740. WinDbg - _NT_SYMBOL_PATH 환경 변수에 설정한 경로로 심벌 파일을 다운로드하지 않는 경우
13868정성태1/17/20253109Windows: 277. Hyper-V - Windows 11 VM의 Enhanced Session 모드로 로그인을 할 수 없는 문제
1  [2]  3  4  5  6  7  8  9  10  11  12  13  14  15  ...