Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 15개 있습니다.)

(번역글) .NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others

이번에도 .NET Internals Cookbook 시리즈의 10번째 글을 번역한 것입니다.

(번역글) .NET Internals Cookbook Part 1 - Exceptions, filters and corrupted processes
; https://www.sysnet.pe.kr/2/0/11838

(번역글) .NET Internals Cookbook Part 2 - GC-related things
; https://www.sysnet.pe.kr/2/0/11869

(번역글) .NET Internals Cookbook Part 3 - Initialization tricks
; https://www.sysnet.pe.kr/2/0/11871

(번역글) .NET Internals Cookbook Part 4 - Type members
; https://www.sysnet.pe.kr/2/0/11872

(번역글) .NET Internals Cookbook Part 5 - Methods, parameters, modifiers
; https://www.sysnet.pe.kr/2/0/11873

(번역글) .NET Internals Cookbook Part 6 - Object internals
; https://www.sysnet.pe.kr/2/0/11874

(번역글) .NET Internals Cookbook Part 7 - Word tearing, locking and others
; https://www.sysnet.pe.kr/2/0/11876

(번역글) .NET Internals Cookbook Part 8 - C# gotchas
; https://www.sysnet.pe.kr/2/0/11877

(번역글) .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff
; https://www.sysnet.pe.kr/2/0/11878

.NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others
; https://blog.adamfurmanek.pl/2019/04/20/net-internals-cookbook-part-10/

(번역글) .NET Internals Cookbook Part 11 - Various C# riddles
; https://www.sysnet.pe.kr/2/0/11882

(번역글) .NET Internals Cookbook Part 12 - Memory structure, attributes, handles
; https://www.sysnet.pe.kr/2/0/11891





65. async void 유형의 메서드에서 발생한 예외 처리 방법

async 메서드는 반환 값이 없는 경우 Task, 있는 경우 Task<T>를 쓰는 것이 권장된다고 아래의 글에서 설명한 적이 있습니다.

async 메서드의 void 반환 타입 사용에 대하여
; https://www.sysnet.pe.kr/2/0/11414

async void의 경우 예외 처리를 하려면 별도의 synchronization context와 task scheduler를 정의해야 합니다. 이에 대해서는 다음의 글에서 잘 설명하고 있습니다.

Async Wandering
; https://blog.adamfurmanek.pl/blog/2018/10/06/async-wandering-part-5-catching-exceptions-from-async-void/

물론 저 글을 읽고 자신만의 synchronization context와 task scheduler를 정의하는 것도 가능하겠지만 기왕이면 이미 만들어져있는 다음의 라이브러리를 쓰는 것도 좋겠습니다.

StephenCleary/AsyncEx
; https://github.com/StephenCleary/AsyncEx





66. Stream은 다중 스레드 환경에 안전할까?

NOPE!





67. Thread.Yield와 Thread.Sleep(0)의 차이점

Thread.Yield는 SwitchToThread Win32 API를 호출하는데, 이 함수는 이것을 실행하는 "현재 프로세서에 ready 상태의 스레드가 있는지 체크" 후 있으면 해당 스레드로 전환이 되지만 없으면 현재 스레드가 계속 실행되도록 합니다. 원 글(.NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others)에서는 이와 함께 SwitchToThread API가 커널 모드로의 전환을 하지 않는다고 하는데... 이게 좀 맞지 않는 설명입니다. 커널 모드로 전환은 하지만, 다른 스레드로 문맥 전환(Context switching)이 발생하지 않을 수 있습니니다.


(20222-04-18 업데이트) https://www.sysnet.pe.kr/2/0/13033

현재 프로세서에 우선순위에 상관없이 ready 상태의 스레드만 없다면 SwitchToThread API의 사용은 CPU 100% 점유를 할 수 있습니다.
반면, Thread.Sleep(0)은 호출 시 무조건 자신에게 남겨진 퀀텀 시간을 포기하고 다음번 스케줄링으로 넘어갑니다. 따라서, ready 상태의 스레드가 다른 프로세스에라도 있기만 한다면 문맥 전환을 가져옵니다. 물론, 없다면 Thread.Sleep(0)을 호출한 스레드는 스케줄링에서 다시 선택되어져서 연이어 실행이 될 것입니다. 하지만 일반적인 스케줄링 정책을 따르는 것이기 때문에 SwitchToThread와는 달리 우선순위가 낮은 스레드에 양보하지는 않습니다. (윈도우는 우선순위가 낮은 스레드가 3~4초 정도 스케줄링에 밀리게 되는 경우 우선순위를 15로 임시 조절을 해 다음번 스케줄링에서 선택되게 합니다.)

또한 원 글(.NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others)에서는 Thread.Sleep(0)의 경우 CPU 사용량을 0으로 낮출 수 없다고 하는데 어차피 SwitchToThread도 마찬가지이므로 굳이 이걸 언급한 이유를 모르겠습니다.


참고로, Thread.Sleep(1)은 최소 1ms를 대기하도록 하지만 아래의 글에 설명한 대로 상황에 따라 달라질 수 있습니다.

윈도우 운영체제의 시간 함수 (2) - Sleep 함수의 동작 방식
; https://www.sysnet.pe.kr/2/0/11065

그건 그렇긴 한데... Thread.Yield와 Thread.Sleep의 특성을 눈에 띄게 구분지을 만한 예제가 없습니다. 가령, 스레드 3개를 생성해 모두 CPU 친화성을 동일한 Processor로 설정해 둔 후 스레드 2개는 우선순위를 낮추고(설령 높여도) 다른 하나는 Yield만을 하는 스레드가 있다면,

using System;
using System.Diagnostics;
using System.Numerics;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static int _processorId = 1;

        static void Main(string[] args)
        {
            Thread t1 = new Thread(yieldProc);
            Thread t2 = new Thread(lowPriorityProc);
            Thread t3 = new Thread(lowPriorityProc);

            t3.Start();
            t2.Start();
            t1.Start();
            t1.Join();
            t2.Join();
        }

        private static void lowPriorityProc()
        {
            SetIdealProcessor(_processorId);

            Console.WriteLine("lowPriorityProc: " + AppDomain.GetCurrentThreadId());
            Thread.CurrentThread.Priority = ThreadPriority.Lowest;

            int i = 0;
            BigInteger sum = new BigInteger();

            while (true)
            {
                i++;
                sum += i;
            }
        }

        private static void yieldProc()
        {
            SetIdealProcessor(_processorId);

            Console.WriteLine("yieldProc: " + AppDomain.GetCurrentThreadId());
            while (true)
            {
                Thread.Yield();
            }
        }

        // https://www.sysnet.pe.kr/2/0/10933
        static void SetIdealProcessor(int cpuNumber)
        {
            if (cpuNumber >= Environment.ProcessorCount)
            {
                cpuNumber = 0;
            }

            foreach (ProcessThread pthread in Process.GetCurrentProcess().Threads)
            {
                if (pthread.Id == AppDomain.GetCurrentThreadId())
                {
                    pthread.ProcessorAffinity = new IntPtr(1 << cpuNumber);
                    break;
                }
            }
        }
    }
}

Yield 쪽 스레드는 CPU 100% 현상을 보이지 않아야 할 것 같은데 아닙니다. 현상을 놓고 가정해 보면 스레드 친화성을 지정했지만 실제로 실행해 보면 다중 코어가 함께 100% 현상으로 나오므로 다른 한쪽 스레드가 Yield를 해도 다른 스레드들이 이미 다른 코어로 옮겨진 상태여서 효과가 나지 않는 것 같습니다. (혹시 Yield, Sleep(0)에 대한 뚜렷한 특성을 테스트할 수 있는 예제를 알고 계신 분은 덧글 부탁드립니다. ^^)





68. .NET 응용 프로그램은 기본적으로 몇 개의 스레드가 생성될까?

간단한 콘솔 프로그램의 경우,

using System;

namespace Program
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Attach WinDBG and check!");
            Console.ReadLine();
        }
    }
}

windbg로 다음과 같이 "~" 명령어로 총 스레드를 확인할 수 있습니다.

0:006> ~
   0  Id: 7180.6a60 Suspend: 1 Teb: 00a25000 Unfrozen
   1  Id: 7180.7a88 Suspend: 1 Teb: 00a28000 Unfrozen
   2  Id: 7180.5a1c Suspend: 1 Teb: 00a2b000 Unfrozen
   3  Id: 7180.7c54 Suspend: 1 Teb: 00a2e000 Unfrozen
   4  Id: 7180.7c28 Suspend: 1 Teb: 00a31000 Unfrozen
   5  Id: 7180.3678 Suspend: 1 Teb: 00a34000 Unfrozen
.  6  Id: 7180.7fa0 Suspend: 1 Teb: 00a37000 Unfrozen

표면상으로 보면 7개가 맞을 것 같지만 아닙니다. 왜냐하면 이 중에서 마지막의 6번 스레드는 windbg를 attach함으로써 생성된 디버그용 스레드이기 때문입니다. 실제로 6번 스레드의 콜 스택을 확인하면 다음과 같이 DbgUiRemoteBreakin인 것을 볼 수 있습니다.

0:006> k
 # ChildEBP RetAddr  
00 0178f7e4 77efb3a9 ntdll!DbgBreakPoint
01 0178f814 76860419 ntdll!DbgUiRemoteBreakin+0x39
02 0178f824 77eb662d KERNEL32!BaseThreadInitThunk+0x19
03 0178f880 77eb65fd ntdll!__RtlUserThreadStart+0x2f
04 0178f890 00000000 ntdll!_RtlUserThreadStart+0x1b

따라서 최소 생성되는 스레드 수는 6개입니다. 그리고 이 중에서 Managed 스레드의 수를 SOS.dll 확장 명령어로 볼 수 있습니다.

0:006> !threads
ThreadCount:      2
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                         Lock  
       ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
   0    1 6a60 00eaa6c8     2a020 Preemptive  02C0461C:00000000 00e9d598 1     MTA 
   5    2 3678 00eba478     2b220 Preemptive  00000000:00000000 00e9d598 0     MTA (Finalizer) 

정리하면 다음과 같이 구분이 됩니다.

0번: Main 함수를 실행하는 스레드
5번: Finalizer를 실행하는 스레드
1, 2, 3, 4번: ThreadPool의 최소 스레드
6번: Windbg의 Debug Thread

여기서 ThreadPool의 최소 스레드 수가 4인데요, 실제로 이것은 다음의 닷넷 코드로 확인할 수 있습니다.

ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
Console.WriteLine(workerThreads); // 출력 결과: 4
Console.WriteLine(completionPortThreads); // 출력 결과: 4

이를 기반으로 예제를 다음과 같이 재구성할 수 있습니다.

using System;
using System.Diagnostics;
using System.Threading;

namespace Program
{
    public class Program
    {
        public static void Main(string[] args)
        {
            ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
            Console.WriteLine($"workerThreads: {workerThreads}");

            OutputThreads();

            Console.WriteLine("Attach WinDBG and check!");
            Console.ReadLine();
        }

        static void OutputThreads()
        {
            int id = AppDomain.GetCurrentThreadId();

            Console.WriteLine();
            int n = 0;
            foreach (ProcessThread thread in Process.GetCurrentProcess().Threads)
            {
                string threadType = "(ThreadPool)";
                if (id == thread.Id)
                {
                    threadType = "(Managed)";
                }

                if (thread.BasePriority == 10)
                {
                    threadType = "(Finalizer)";
                }

                Console.WriteLine($"[{n}] {thread.Id}, (0x" + thread.Id.ToString("x") + $") {threadType}");
                n++;
            }
        }
    }
}
/* 출력 결과
workerThreads: 4

[0] 31708, (0x7bdc) (Managed)
[1] 25456, (0x6370) (ThreadPool)
[2] 19776, (0x4d40) (ThreadPool)
[3] 28448, (0x6f20) (ThreadPool)
[4] 29228, (0x722c) (ThreadPool)
[5] 30808, (0x7858) (Finalizer)
Attach WinDBG and check!
*/

그런데 간혹 ThreadPool의 스레드 수가 3으로 출력되는 경우를 볼 수 있습니다. 아마도 스레드 풀의 스레드 생성에 대한 지연 시간이 있는 듯도 한데 정확하진 않습니다.

참고로 다음의 글도 한번 읽어보시고. ^^

.NET 응용 프로그램에 기본 생성되는 스레드들에 대한 탐구
; https://www.sysnet.pe.kr/2/0/1247





69. 스레드가 차지하는 기본 메모리?

스레드의 생성으로 사용자 공간에서는 1MB의 스택 크기가 소비되고, 커널 공간에는 12kB(x86) 또는 24kB(x64)가 소비됩니다. 그 외, 만약 64비트운영체제에서 실행되는 32비트 프로세스라면 WoW64 계층으로 별도의 스택 공간이 사용됩니다.

다음의 글을 보면,

Increasing the Size of your Stack (.NET Memory Management: Part 3)
; http://content.atalasoft.com/atalasoft-blog/increasing-the-size-of-your-stack-net-memory-management-part-3

기본 스택 크기를 (.NET 2.0부터 지원되는) Thread 생성자의 2번째 인자를 지정해 변경하는 방법이 나옵니다.

Thread t = new Thread(threadFunc, 1024 * 1024); // 기본 1MB, 또는 0으로 지정
                                                // 최소 262,144(256kB)

또는, editbin을 이용해 기본 크기를 어셈블리 파일 생성 후 PE 헤더를 변경하는 것으로도 지정할 수 있습니다.

EDITBIN.EXE /STACK:524288 file.exe





70. 예외가 발생하는 async Task 메서드를 await으로 호출하지 않는다면?

async Task 메서드에 발생한 예외는 await 호출을 하지 않은 응용 프로그램에는 전달되지 않습니다. 대신, TaskScheduler.UnobservedTaskException 이벤트를 구독하는 경우 Task에서 발생한 예외를 보관한 TaskExceptionHolder 객체가 GC되는 시점에 Finalizer의 호출에 의해 예외를 검출할 수 있습니다.

using System;
using System.Threading.Tasks;

namespace Program
{
    class Program
    {
        static void Main(string[] args)
        {
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
            Test();

            Console.ReadLine();

            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            Console.WriteLine("Unobserved exception!");
            Console.WriteLine(e.Exception);
        }

        public static async Task Test()
        {
            throw new Exception();
        }
    }
}

참고로 TaskScheduler_UnobservedTaskException 발생 시점의 call-stack은 다음과 같습니다.

    ConsoleApp1.exe!Program.Program.TaskScheduler_UnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs e) Line 20 C#
    mscorlib.dll!System.Threading.Tasks.TaskScheduler.PublishUnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs ueea) Line 521  C#
>    mscorlib.dll!System.Threading.Tasks.TaskExceptionHolder.~TaskExceptionHolder() Line 145 C#





71. CLR이 fiber를 지원할까?

저도 이에 대해 전에 글을 쓴 적이 있는데요,

ManagedThreadId - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/492

공식적으로도 현재 지원을 안 하고 있지만, .NET 2.0에서 fiber 지원을 하려다 포기했는데 4.8까지 오는 동안 이 부분으로 전혀 소식이 없는 것을 보면 향후에도 매우 어려울 것으로 보입니다.





72. Thread.Yield나 Thread.Join은 COM 메시지를 처리할까?

다음의 글에 보면,

Which blocking operations cause an STA thread to pump COM messages?
; https://stackoverflow.com/questions/21571598/which-blocking-operations-cause-an-sta-thread-to-pump-com-messages

다음의 동작들에서 Win32 메시지 처리가 된다고 합니다.

  • Thread.Join
  • WaitHandle.WaitOne/WaitAny/WaitAll
  • GC.WaitForPendingFinalizers
  • Monitor.Enter
  • ReaderWriterLock
  • BlockingCollection

보는 바와 같이 일단 Thread.Sleep이나 Thread.Yield에서는 메시지 처리가 없습니다. 하지만 위의 목록에 있다 해도 모든 종류의 Win32 메시지가 처리된다는 것은 아니므로 COM 메시지가 처리되는지는 꼭 테스트를 해보는 것이 좋습니다.

참고로, COM 메시지 처리에 메시지 루프를 이용하는 것이 가장 확실합니다.

C# COM 서버에 구독한 COM 이벤트를 C++에서 받는 방법
; https://www.sysnet.pe.kr/2/0/11679

C# - 작업자 스레드와 UI 스레드
; https://www.sysnet.pe.kr/2/0/11287





73. Thread.Abort의 동작 원리

Abort 호출로 인한 동작은 다음과 같습니다.

  1. OS 스레드를 (종료가 아닌 일시) 중지,
  2. 중지된 스레드의 관리 객체에 Abort를 요구한다는 메타데이터 비트를 설정,
  3. APC 함수를 APC 큐에 추가하고 스레드 실행을 재개
  4. 스레드가 동작하면서 APC 큐에 추가된 함수를 실행할 수 있는 "alertable" 상태로 진입하기를 기대, 해당 APC 함수가 실행되면 메타데이터 비트가 설정되었는지 확인하고 예외를 발생
  5. 만약 일정 시간 내에 alertable 상태로 진입하지 않으면 강제로 IP 레지스터의 값을 변경해 스레드의 실행을 가로채서 처리

다음의 글에 쓰인 Thread.Abort에 대한 설명도 참고하시고.

Special Exceptions In .NET And How To Prepare Them
; https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fchepa.net%2Fall%2F2018%2F10%2F11%2F%D0%BE%D1%81%D0%BE%D0%B1%D1%8B%D0%B5-%D0%B8%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B2-net-%D0%B8-%D0%BA%D0%B0%D0%BA-%D0%B8%D1%85-%D0%B3%D0%BE%D1%82%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%2F

원문 - Особые исключения в .NET и как их готовить
; https://habr.com/ru/company/jugru/blog/426045/





74. CLR 메모리 모델(Memory model)

다음의 글이 좋은 참고가 될 것입니다.

Memory Model
; https://learn.microsoft.com/en-us/archive/blogs/cbrumme/memory-model

CLR Memory Model
; https://learn.microsoft.com/en-us/archive/blogs/jaredpar/clr-memory-model

CLR 2.0 Memory Model
; http://joeduffyblog.com/2007/11/10/clr-20-memory-model/

Memory Barriers in .NET
; https://afana.me/archive/2015/07/10/memory-barriers-in-dot-net.aspx/

이글에서는 ECMA 메모리 모델과 CLR 2.0 메모리 모델의 규칙을 간단하게 살펴 봅니다.


ECMA 메모리 모델

  1. 모든 기본형은 정렬되지만(short의 경우 2바이트 정렬, int32/float32의 경우 4바이트, int64/float64는 하드웨어 아키텍처에 따라 4/8바이트) "unaligned" 명령어를 사용하면 정렬을 바꿀 수 있다.
  2. 바이트 순서(Byte ordering)는 하드웨어 아키텍처에 의존
  3. 런타임은 스레드에서 발생하는 모든 side effect와 예외가 CIL 명령어 순서로 실행되는 것을 보장
  4. native int 크기 내에서는 atomic하게 데이터가 이동
  5. volatile read는 acquire semantics
  6. volatile write는 release semantics

위의 글에서 acquire semantics와 release semantics에 대해서는 다음의 글을 참고하시면 좋습니다.

C/C++ volatile 키워드
; https://skyul.tistory.com/337

[C++] Volatile 키워드
; https://aram88.tistory.com/entry/C-Volatile-%ED%82%A4%EC%9B%8C%EB%93%9C



  1. 로드/저장 간의 데이터 의존성은 보존된다. (Data dependence among loads and stores is never violated.)
  2. 모든 저장은 release semantics
  3. 모든 volatile load는 acquire semantics
  4. 로드/저장은 barrier(예: Thread.MemoryBarrier, lock 획득, Interlocked.Exchange, Interlocked.CompareExchange,... 등)를 넘어 재정렬 될 수 없다.
  5. Loads and stores to the heap may never be introduced.
  6. 로드/저장은 같은 위치로의 근접한 로드나 저장 시 합쳐져서 삭제될 수 있다.(Loads and stores may only be deleted when coalescing adjacent loads and stores from/to the same location)

위의 내용은 다음의 글에 대한 요약이라고 하니,

[CHM] MSDN Magazine 2005년 10월
- Understand the Impact of Low-Lock Techniques in Multithreaded Apps
; http://download.microsoft.com/download/3/a/7/3a7fa450-1f33-41f7-9e6d-3aa95b5a6aea/MSDNMagazineOctober2005en-us.chm

세부적인 의미는 CHM 파일에 있는 글을 읽어보는 것이 도움이 될 것입니다. 이외에도 MSDN Magazine 2012년 12월, 2013년 1월의 기사를 참고하면 좋습니다.

C# - The C# Memory Model in Theory and Practice
; https://learn.microsoft.com/en-us/archive/msdn-magazine/2012/december/csharp-the-csharp-memory-model-in-theory-and-practice

C# - The C# Memory Model in Theory and Practice, Part 2
; https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/january/csharp-the-csharp-memory-model-in-theory-and-practice-part-2

저도 한번 언제 마음 먹고 이 내용을 정리해야 하는데... 차일피일 미루기만 하는군요. ^^;



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

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/1/2023]

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

비밀번호

댓글 작성자
 




... 31  32  33  [34]  35  36  37  38  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12770정성태8/11/20218413.NET Framework: 1086. C# - Windows Forms 응용 프로그램의 자식 컨트롤 부하파일 다운로드1
12769정성태8/11/20216404오류 유형: 752. Python - ImportError: No module named pip._internal.cli.main 두 번째 이야기
12768정성태8/10/20217423.NET Framework: 1085. .NET 6에 포함된 신규 BCL API [1]파일 다운로드1
12767정성태8/10/20218473오류 유형: 752. Python - ImportError: No module named pip._internal.cli.main
12766정성태8/9/20217010Java: 32. closing inbound before receiving peer's close_notify
12765정성태8/9/20216326Java: 31. Cannot load JDBC driver class 'org.mysql.jdbc.Driver'
12764정성태8/9/202144789Java: 30. XML document from ServletContext resource [/WEB-INF/applicationContext.xml] is invalid
12763정성태8/9/20217783Java: 29. java.lang.NullPointerException - com.mysql.jdbc.ConnectionImpl.getServerCharset
12762정성태8/8/202111304Java: 28. IntelliJ - Unable to open debugger port 오류
12761정성태8/8/20218524Java: 27. IntelliJ - java: package javax.inject does not exist [2]
12760정성태8/8/20215939개발 환경 구성: 594. 전용 "Command Prompt for ..." 단축 아이콘 만들기
12759정성태8/8/20219070Java: 26. IntelliJ + Spring Framework + 새로운 Controller 추가 [2]파일 다운로드1
12758정성태8/7/20218410오류 유형: 751. Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode)
12757정성태8/7/20219096Java: 25. IntelliJ + Spring Framework 프로젝트 생성
12756정성태8/6/20217915.NET Framework: 1084. C# - .NET Core Web API 단위 테스트 방법 [1]파일 다운로드1
12755정성태8/5/20217052개발 환경 구성: 593. MSTest - 단위 테스트에 static/instance 유형의 private 멤버 접근 방법파일 다운로드1
12754정성태8/5/20217988오류 유형: 750. manage.py - Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
12753정성태8/5/20218195오류 유형: 749. PyCharm - Error: Django is not importable in this environment
12752정성태8/4/20216309개발 환경 구성: 592. JetBrains의 IDE(예를 들어, PyCharm)에서 Visual Studio 키보드 매핑 적용
12751정성태8/4/20219346개발 환경 구성: 591. Windows 10 WSL2 환경에서 docker-compose 빌드하는 방법
12750정성태8/3/20216149디버깅 기술: 181. windbg - 콜 스택의 "Call Site" 오프셋 값이 가리키는 위치
12749정성태8/2/20215570개발 환경 구성: 590. Visual Studio 2017부터 단위 테스트에 DataRow 특성 지원
12748정성태8/2/20216184개발 환경 구성: 589. Azure Active Directory - tenant의 관리자(admin) 계정 로그인 방법
12747정성태8/1/20216784오류 유형: 748. 오류 기록 - MICROSOFT GRAPH – HOW TO IMPLEMENT IAUTHENTICATIONPROVIDER파일 다운로드1
12746정성태7/31/20218764개발 환경 구성: 588. 네트워크 장비 환경을 시뮬레이션하는 Packet Tracer 프로그램 소개
12745정성태7/31/20216620개발 환경 구성: 587. Azure Active Directory - tenant의 관리자 계정 로그인 방법
... 31  32  33  [34]  35  36  37  38  39  40  41  42  43  44  45  ...