Microsoft MVP성태의 닷넷 이야기
.NET Framework: 497. .NET Garbage Collection에 대한 정리 [링크 복사], [링크+제목 복사]
조회: 20553
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 9개 있습니다.)
.NET Framework: 497. .NET Garbage Collection에 대한 정리
; https://www.sysnet.pe.kr/2/0/1862

.NET Framework: 728. windbg - 눈으로 확인하는 Workstation GC / Server GC
; https://www.sysnet.pe.kr/2/0/11445

.NET Framework: 729. windbg로 살펴보는 GC heap의 Segment 구조
; https://www.sysnet.pe.kr/2/0/11446

.NET Framework: 1026. 닷넷 5에 추가된 POH (Pinned Object Heap)
; https://www.sysnet.pe.kr/2/0/12545

.NET Framework: 1029. C# - GC 호출로 인한 메모리 압축(Compaction)을 확인하는 방법
; https://www.sysnet.pe.kr/2/0/12572

.NET Framework: 1059. 세대 별 GC(Garbage Collection) 방식에서 Card table의 사용 의미
; https://www.sysnet.pe.kr/2/0/12649

.NET Framework: 1060. 닷넷 GC에 새롭게 구현되는 DPAD(Dynamic Promotion And Demotion for GC)
; https://www.sysnet.pe.kr/2/0/12653

.NET Framework: 2024. .NET 7에 도입된 GC의 메모리 해제에 대한 segment와 region의 차이점
; https://www.sysnet.pe.kr/2/0/13083

닷넷: 2209. .NET 8 - NonGC Heap / FOH (Frozen Object Heap)
; https://www.sysnet.pe.kr/2/0/13536




.NET Garbage Collection에 대한 정리

우선, GC는 크게 "Workstation GC", "Server GC"로 나뉘고 다음과 같은 특징을 지닙니다.

Workstation GC: GC Managed Heap은 EXE 프로세스 당 1개가 생성됨. 
                당연히 EXE 프로세스 내의 모든 스레드는 그 하나의 Heap에 관리 개체를 할당하게 됩니다.

                Workstation GC의 경우 다시 Concurrent 기능 유무에 따라 다음과 같이 나뉩니다.

                1. Workstation GC: 1 heap
                2. Workstation GC + Concurrent: 1 heap and 1 GC thread

Server GC: 논리 프로세서 1개당 1개의 GC Managed Heap 생성. 
                (예를 들어, "쿼드 코어"에서 "Hyper Threading"이 활성화 되어 있다면 총 8개의 논리 프로세서가 있으므로 8개의 GC Managed Heap 생성)
                (일단 힙이 n개 생성되면, 이후 프로세스가 종료하는 동안 그 수는 변경되지 않는다.)
            Heap 간의 참조 가능
                (예를 들어, 1번 Heap에 있는 객체는 2번 Heap에 있는 객체를 참조하는 것이 가능.)
            GC 힙에 대한 정리 작업이 동시 수행됨.
                (예를 들어, 2개의 GC Managed Heap이 있고 그것들의 총 용량이 100MB일 때 이상적인 경우 각각 50MB씩 GC 수행 작업이 동시에 수행됨.)
                (따라서, Workstation GC라면 100MB GC 작업에 10초가 걸렸다면 Server GC는 5초만에 완료 가능)

관리 힙에 대해서 정리하면 다음과 같습니다.

GC는 연속된 주소를 갖는 메모리 영역을 VirtualAlloc을 이용해 예약하고 이 한 개의 단위를 Segment라 한다.
    세그먼트의 경우, 필요에 의해 reserved 범위 내에서 committed/decommitted가 되거나 free될 수 있다.
    Segment라 불리는 메모리의 크기는 구현하기 나름이다. 
    즉, 특정 환경에서 Segment 크기를 알았다고 해서 다른 .NET 버전(심지어 업데이트까지도)에서도 같을 거라 가정해서는 안된다.

GC는 필요한 만큼 Segment를 할당할 수 있고 필요없어지면 (VirtualFree로) OS에 반납한다.

Heap은 대체로 85,000 바이트 이상의 객체들을 위해 LOH(Large Object Heap)와, 그 이하의 객체들을 담는 힙으로 나뉩니다. LOH의 경우, dead object에 대해 compacting 단계를 기본적으로 수행하지 않지만 .NET 4.5.1 이후 GCSettings.LargeObjectHeapCompactionMode 속성을 이용해 compacting이 가능하게 되었습니다. (아시는 것처럼, LOH는 Gen 2 GC 수행 시에 함께 GC됩니다.)

Heap은 세대(generation)별로 나뉘어 구성되는데, 0과 1세대는 ephemeral generations라고도 불립니다. 새로운 객체들은 "ephemeral segment"라고 알려진 segment에 할당되고, 살아남아 2세대까지 진행합니다. 최초 1개의 segment가 할당된 경우, 동시에 ephemeral segment가 되지만 시간이 지날수록 2세대 객체가 해당 segment를 잠식하게 되고 GC는 다시 새롭게 segment를 만듭니다. 그럼, 새롭게 만들어진 segment가 다시 "ephemeral segment"로 여겨지고 새로운 객체들은 거기에 할당됩니다. 따라서, 초기 시점에는 0, 1, 2 세대들이 하나의 segment에 있게 되지만, 시간이 지나면 2세대만 있는 segment로 변화하게 됩니다. 프로세스가 필요로 하고 메모리가 허용하는 한 2세대 segment는 그만큼 늘어나게 됩니다.

따라서, 1세대와 2세대 생존 객체들은 현재의 segment가 아닌 이전의 segment로 이전할 수도 있습니다. (아마도, 0세대는 해당 segment에 1세대가 없다고 해도 ephemeral segment의 관리 정책 상 다른 segment로는 이전하지 않는 듯!)

Gen 1/Gen 0의 크기는 절대로 segment를 넘을 수 없는데, 왜냐하면 상식적으로 GC가 발생하면 Gen 2로 넘어가 버리기 때문입니다. 또한, 생존 객체율이 일정 기준을 넘어서면 세대의 크기를 증가시킨다고 합니다.

CLR은 응용 프로그램의 working set 메모리가 너무 크지 않게, GC의 동작 시간이 너무 길지 않게 조정하는 역할을 합니다.

GC는 생존 개체에 대한 결정을 "Stack roots", "GC Handles", "Static data"를 근간으로 판단하는데,

Stack roots: Stack variables provided by the just-in-time (JIT) compiler and stack walker.

Garbage collection handles: Handles that point to managed objects and that can be allocated by user code or by the common language runtime.

Static data: Static objects in application domains that could be referencing other objects. Each application domain keeps track of its static objects.

(기본적인 동작 방식으로 본다면) GC가 수행되기 전, GC를 발생시킨 스레드를 제외한 다른 모드 스레드들은 정지합니다. 이를 그림으로 도식하면 다음과 같습니다.

[그림 1: GC 기본 동작]
clr_gc_1.png

소멸자(finalizer)가 정의된 객체가 생성된 경우 특별히 종료 큐(Finalization Queue)에 객체를 함께 등록합니다. 그와 함께 해당 객체는 다음 세대로 살아남는 효과가 적용됩니다. 왜냐하면, 그 객체에 대한 참조를 종료 큐에서 가지고 있는 것과 같기 때문입니다. (따라서, GC 생존을 결정하는 것은 "Stack roots, Garbage Collection Handles, Static data" 뿐만 아니라 종료 큐도 체크 대상이 됩니다.) 종료 큐 이외의 참조가 없어진 경우, GC는 종료 큐로부터 객체를 꺼내 Freachable 큐에 다시 객체를 보관합니다. 그리고 Freachable 큐에 보관된 객체는 CLR이 미리 생성해 둔 스레드에 의해 꺼내지고 소멸자가 호출됩니다. 이런 과정이 완료되고서야 마침내 다음 GC 수행이 되었을 때 비로소 Managed Heap에서 객체가 삭제됩니다. (참고로, 소멸자에 대한 처리 방식은 "시작하세요! C# 프로그래밍" 책에서 "5.4.2.6 소멸자" 편에도 소개하고 있습니다.)




모드별 GC의 행동에 대해 좀 더 살펴보겠습니다. 우선 가장 기본적인 Workstation GC입니다.

Workstation GC(garbage collection): 클라이언트 응용 프로그램에서 사용되는 방식으로 <gcServer>의 기본 설정값

다시 Workstation GC는 concurrent와 non-concurrent로 나뉩니다. (.NET 3.5 이하에서는 다음과 같은 2가지 모드의 GC가 있음.)

- Workstation non-concurrent GC (1개의 논리 CPU에서 실행되는 서버 응용 프로그램을 위한 GC)
0,1,2세대와 상관없이 GC 작업이 수행되면 다른 모든 관리 스레드의 수행을 중지, 현재 거의 사용되지 않는 모드임. (n-코어 CPU가 일반적인 요즘 서버 컴퓨터에 1-core로 된 1-cpu를 사용하는 경우는 거의 없으므로.) "[그림 1: GC 기본 동작]"과 동일.

- Workstation concurrent GC (윈폼/윈도우 서비스 프로그램을 위한 기본 설정값)
Concurrent GC는 GC 수행 시에 다른 스레드들도 함께 실행되는 것을 허용. ASP.NET 환경이 아닌 경우, 일반적인 모든 응용 프로그램은 이 모드를 사용함.
0,1,2세대와 상관없이 GC 작업이 수행되면 다른 모든 스레드의 수행을 중지, 하지만 0,1 세대에 대해서만 GC를 유발한 스레드가 GC 작업을 수행하다가 2세대 힙 메모리를 GC하는 동안에는 다른 모든 스레드의 실행을 재개하고 GC 작업을 별도의 dedicated GC 스레드로 넘겨서 실행.


Workstation concurrent GC의 경우, 이상적인 GC 상황은 다음과 같습니다.

[그림 2: 이상적인 Workstation concurrent GC의 동작 방식(http://www.simpleisbest.net/post/2011/04/25/Garbage-Collection-Modes.aspx])
clr_gc_2-1.png

하지만, dedicated GC 스레드가 2세대 힙 영역을 정리하는 동안, 다른 스레드들이 0,1 세대에 객체를 할당하다 보면 다시 ephemeral segment의 용량을 모두 소비할 수도 있는 상황이 올 수 있고, 그럼 어쩔 수 없이 모든 스레드들이 중지됩니다. 그래서, 가끔은 다음과 같은 GC 상황이 펼쳐지기도 합니다.

[그림 3: Workstation concurrent GC의 또 다른 동작 사례]
clr_gc_2-2.png

Workstation concurrent GC는 이후 .NET 4.0에서 새롭게 도입된 background garbage collection으로 대체됩니다. 즉, .NET 4.0부터 다음과 같은 2가지 모드의 GC가 있습니다.

  • Workstation non-concurrent GC
  • Workstation background GC

결국 정리해 보면, .NET 4.0 응용 프로그램의 경우 ASP.NET 환경이 아닌, 일반적으로 여러분들이 만드는 모든 응용 프로그램은 "Workstation background GC" 모드를 사용한다고 보면 됩니다.

"Workstation background GC"는 dedicated GC 스레드가 2세대 힙 영역을 정리하는 동안, 다른 스레드들이 0,1 세대에 객체를 할당하다 보면 다시 ephemeral segment의 용량을 모두 소비할 수도 있는 상황이 오는데, 이 때가 되면 2세대 힙 정리를 멈추고 다시 0,1 세대 힙 정리를 시작/완료하고 지난 2세대 힙 정리를 재개하도록 바뀌었습니다.

[그림 4: Workstation background GC의 동작 방식]
clr_gc_4.png

background GC에서 새롭게 "foreground GC"라는 용어가 생기는데, 이것은 2세대 힙을 정리하는 동안 수행되는 0,1세대 힙의 정리를 하는 GC를 의미합니다.




이제 Server GC의 특징을 보겠습니다.

  • 서버 응용 프로그램을 위한 GC
  • 서버 GC의 경우 "non-concurrent"와 닷넷 4.5이후부터 지원되는 "background"로 나뉨.
  • 서버 GC는 Workstation GC보다 segment 크기를 더 늘릴 수 있음.
  • 주의할 점은, 서버 GC가 프로세서 당 dedicated GC 스레드를 갖기 때문에 시스템에 서버 GC를 사용하는 EXE 프로세스가 많은 경우 자원 소비가 클 수 있음. 예를 들어, 4-core에 Server GC를 사용하는 12개의 EXE 프로세스가 있다면 총 48개의 dedicated GC 스레드가 생성되고, 전체적인 시스템 메모리 부족 현상으로 진입하게 되는 경우 자칫 48개의 GC 스레드가 구동되며 더욱 큰 부하를 가져오게 됨.
  • dedicated GC 스레드는 THREAD_PRIORITY_HIGHEST 우선 순위

Server GC는 .NET 4.5 미만에서는 "non-concurrent" 유형만 제공되는데, Workstation non-concurrent GC와 다른 점은 논리 프로세서마다 GC Heap과 Dedicated GC 스레드가 생성되므로, GC 수행시 모든 dedicated GC 스레드가 함께 각각 맡은 GC Heap을 정리하며, 그 외의 다른 모든 스레드들은 정지시킨다는 특징이 있습니다. 이를 그림으로 표현하면 다음과 같습니다.

[그림 5: 이상적인 Server GC의 동작 상황]
clr_gc_3-1.png

위의 그림은 GC 스레드가 맡은 크기가 거의 비슷하다는 이상적인 상황이고, 현실적으로는 아래와 같이 GC 스레드들마다 정리 작업에 끝나는 시간이 다를 것이고, 완료 기준은 가장 길게 수행된 GC 스레드가 될 것입니다. (그 외에도, 프로세서 자원은 더 높은 우선 순위의 스레드들에 할당될 수 있으므로.)

[그림 6: 일반적인 Server GC의 동작 상황: (http://www.simpleisbest.net/post/2011/04/25/Garbage-Collection-Modes.aspx)]
clr_gc_3-2.png

단일 모드만 지원해 외롭기만 보였던 Server GC 유형에, .NET 4.5부터 Concurrent 성격이 부여된 "Background GC" 유형이 추가됩니다. 이는 함께 개선된 "Workstation background GC"와 비슷한데, 다른 점이라면 "Dedicated GC 스레드"와 함께 "Background GC 스레드"가 별도로 존재한다는 점입니다.

clr_gc_5.png

어찌되었든, .NET 4.5 이후부터는 Workstation, Server 모두에 "background GC"가 포함되었습니다.

가장 중요한 Server GC의 특징은 다중 프로세서인 경우에만 허용된다는 점입니다. (물론, 요즘의 현실에 비춰봤을 때 1개의 논리 프로세서를 갖는 경우는 거의 없으므로 그냥 잊어도 되는 특징이 되었습니다.) 만약 싱글 프로세서 시스템에서 Server GC를 지정하면 Workstation non-concurrent GC를 사용하도록 강제로 바뀝니다.


간략하게 GC 모드를 정리한 표가 "How does the GC work and what are the sizes of the different generations?" 글에 나오는데 인용해 보겠습니다. ^^

Concurrent WS Non-Concurrent WS Server GC
Design Goal

Balance throughput and responsiveness for client apps with UI

Maximize throughput on single-proc machines

Maximize throughput on MP machines for server apps that create multiple threads to handle the same types of requests
Number of heaps 1 1 1 per processor (HT aware)
GC threads The thread which performs the allocation that triggers the GC The thread which performs the allocation that triggers the GC 1 dedicated GC thread per processor
EE Suspension EE is suspended much shorter but several times during a GC EE is suspended during a GC EE is suspended during a GC
Config setting <gcConcurrent enabled="true"> <gcConcurrent enabled="false"> <gcServer enabled="true">
On a single proc WS GC + non-concurrent




이제 관련 설정값들을 좀 볼까요? ^^ NT 서비스와 같은 서버 응용 프로그램에서 Server GC를 사용하고 싶다면 명시적으로 app.config에 다음과 같이 지정해야 합니다.

<configuration>
 <runtime>
   <gcServer enabled="true" />
 </runtime>
</configuration>

하지만, ASP.NET의 경우에는 기본으로 설정되어 있으므로 상관없습니다. concurrent는 기본 값이 켜져 있으므로 명시적으로 끄고 싶다면 다음과 같이 설정합니다.

<configuration>
 <runtime>
   <gcConcurrent enabled="false" />
 </runtime>
</configuration>




나중에 시간이 되면 GC Heap에 대해 windbg 차원에서 분석하는 것을 해볼텐데, 현재는 그냥 아래와 같은 정도로만 "!eeheap -gc" 명령어의 결과로 나온다고만 알고 지나가겠습니다.

 Heap 0 (001c3a88)
generation 0 starts at 0x0310d288
generation 1 starts at 0x030ee154
generation 2 starts at 0x03030038
ephemeral segment allocation context: none
segment   begin    allocated size                reserved
001c92f0  7a733370 7a754b98  0x00021828(137,256) 00004000
001c5428  790d8620 790f7d8c  0x0001f76c(128,876) 00004000
03030000  03030038 03115294  0x000e525c(938,588) 03d3f000
Large object heap starts at 0x0b030038
segment   begin     allocated  size                    reserved
0b030000 0b030038 0b4d5aa8 0x004a5a70(4,872,816) 01af8000
Heap Size 0x5cbc60(6,077,536)

그런데, 그냥 지나가려니 너무나 특이한 점이 눈에 띄는데요. 03030000 segment 앞에 2개의 이상한 segment가 있는데, 이것은 문자열 상수 데이터를 보관한다고 합니다. 그 정도만 이번엔 언급하고 넘어가겠습니다. ^^

참고로, GC에 대한 보다 많은 정보를 원하시는 분들은 아래의 글들을 꼭 읽어보시길 바랍니다. ^^

닷넷 가비지 컬렉션 다시 보기 - Part I 기본 작동 방식
; http://www.simpleisbest.net/post/2011/04/01/Review-NET-Garbage-Collection.aspx

닷넷 가비지 컬렉션 다시 보기?Part II 세대별 가비지 콜렉션
; http://www.simpleisbest.net/post/2011/04/05/Generational-Garbage-Collection.aspx

닷넷 가비지 컬렉션 다시 보기 - Part III LOH
; http://www.simpleisbest.net/post/2011/04/11/Large-Object-Heap-Intro.aspx

닷넷 가비지 컬렉션 다시 보기 - Part IV 가비지 컬렉션 발생 시기
; http://www.simpleisbest.net/post/2011/04/18/When-GC-Occurs.aspx

닷넷 가비지 컬렉션 다시 보기 - Part V 가비지 컬렉션 모드
; http://www.simpleisbest.net/post/2011/04/25/Garbage-Collection-Modes.aspx

닷넷 가비지 컬렉션 다시 보기 - Part VI 가비지 컬렉션 모드 활용
; http://www.simpleisbest.net/post/2011/04/27/Using-Garbage-Collection-Modes.aspx

How does the GC work and what are the sizes of the different generations?
; http://blogs.msdn.com/b/tess/archive/2008/04/17/how-does-the-gc-work-and-what-are-the-sizes-of-the-different-generations.aspx

The .NET Framework 4.5 includes new garbage collector enhancements for client and server apps
; http://blogs.msdn.com/b/dotnet/archive/2012/07/20/the-net-framework-4-5-includes-new-garbage-collector-enhancements-for-client-and-server-apps.aspx

Fundamentals of Garbage Collection
; https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals





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

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/24/2023]

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

비밀번호

댓글 작성자
 



2016-06-24 06시39분
[kernel] Freachable Queue에서 Freachable이 처음보는 단어라서 한참 찾았는데 FinalizationReachable 을 뜻하는 말이었군요! :)
[guest]
2016-06-27 12시05분
그러고 보니... 제가 그 단어를 당연한 듯이 썼군요. ^^;
정성태
2016-06-29 01시06분
정성태
2021-01-04 09시25분
Spying on .NET Garbage Collector with .NET Core EventPipes
; https://medium.com/criteo-labs/spying-on-net-garbage-collector-with-net-core-eventpipes-9f2a986d5705

----------------------------


Implementing a simple garbage collector in C# - Part 1
; https://www.elementsofcomputerscience.com/posts/garbage-collection-in-csharp-01/

Implementing a simple garbage collector in C# - Part 2
; https://www.elementsofcomputerscience.com/posts/garbage-collection-in-csharp-02/

Implementing a simple garbage collector in C# - Part 3
; https://www.elementsofcomputerscience.com/posts/garbage-collection-in-csharp-03/

Implementing a simple garbage collector in C# - Part 4
; https://www.elementsofcomputerscience.com/posts/garbage-collection-in-csharp-04/

Implementing a simple garbage collector in C# - Part 5
; https://www.elementsofcomputerscience.com/posts/garbage-collection-in-csharp-05/

Implementing a simple garbage collector in C# - Part 6
; https://www.elementsofcomputerscience.com/posts/garbage-collection-in-csharp-06/

Implementing a simple garbage collector in C# - Part 7
; https://www.elementsofcomputerscience.com/posts/garbage-collection-in-csharp-07/
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13597정성태4/15/2024278닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/2024505닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/2024490닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/2024692닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/2024910닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241185C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241152닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241067Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241129닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241184닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신파일 다운로드1
13587정성태3/27/20241131오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241257Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
13585정성태3/26/20241087Windows: 262. PerformanceCounter의 InstanceName에 pid를 추가한 "Process V2"
13584정성태3/26/20241042개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법파일 다운로드1
13583정성태3/25/20241143Windows: 261. CPU Utilization이 100% 넘는 경우를 성능 카운터로 확인하는 방법
13582정성태3/19/20241217Windows: 260. CPU 사용률을 나타내는 2가지 수치 - 사용량(Usage)과 활용률(Utilization)파일 다운로드1
13581정성태3/18/20241362개발 환경 구성: 707. 빌드한 Unity3D 프로그램을 C++ Windows Application에 통합하는 방법
13580정성태3/15/20241131닷넷: 2231. C# - ReceiveTimeout, SendTimeout이 적용되지 않는 Socket await 비동기 호출파일 다운로드1
13579정성태3/13/20241493오류 유형: 899. HTTP Error 500.32 - ANCM Failed to Load dll
13578정성태3/11/20241619닷넷: 2230. C# - 덮어쓰기 가능한 환형 큐 (Circular queue)파일 다운로드1
13577정성태3/9/20241850닷넷: 2229. C# - 닷넷을 위한 난독화 도구 소개 (예: ConfuserEx)
13576정성태3/8/20241539닷넷: 2228. .NET Profiler - IMetaDataEmit2::DefineMethodSpec 사용법
13575정성태3/7/20241661닷넷: 2227. 최신 C# 문법을 .NET Framework 프로젝트에 쓸 수 있을까요?
13574정성태3/6/20241551닷넷: 2226. C# - "Docker Desktop for Windows" Container 환경에서의 IPv6 DualMode 소켓
13573정성태3/5/20241560닷넷: 2225. Windbg - dumasync로 분석하는 async/await 호출
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...