<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>EveryDay.DevUp</title>
    <link>https://everyday-devup.tistory.com/</link>
    <description>Unity로 게임 제작 시 참고할 만한 자료를 모음</description>
    <language>ko</language>
    <pubDate>Thu, 14 May 2026 06:59:40 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>EveryDay.DevUp</managingEditor>
    <image>
      <title>EveryDay.DevUp</title>
      <url>https://tistory1.daumcdn.net/tistory/3864015/attach/83e3cf2cfccb435685e6a82792a40d1d</url>
      <link>https://everyday-devup.tistory.com</link>
    </image>
    <item>
      <title>[PART13.비동기와 스레딩 기초(15/15)] 비동기&amp;middot;병렬 프로그래밍을 처음 만났을 때 지켜야 할 4가지</title>
      <link>https://everyday-devup.tistory.com/585</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(15/15)] 비동기&amp;middot;병렬 프로그래밍을 처음 만났을 때 지켜야 할 4가지&lt;/h1&gt;
&lt;p style=&quot;color: #868e96; font-size: 14px; margin: 0 0 32px; line-height: 1.6;&quot; data-ke-size=&quot;size16&quot;&gt;PART 13의 마무리 정리편입니다. 앞 14개 주제에서 다룬 동기/비동기, 스레드, Task, async/await, lock 같은 개념들을 신입 개발자가 실제 코드를 짤 때 가장 자주 발등을 찍는 &lt;b&gt;4가지 규칙&lt;/b&gt;으로 압축했습니다. 각 규칙은 &quot;왜 필요한가 &amp;rarr; 위반 사례 &amp;rarr; 올바른 코드 &amp;rarr; 한 줄 요약&quot; 순서로 정리했습니다.&lt;/p&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;TL;DR &amp;mdash; 한눈에 보는 4가지 규칙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;비유로 먼저 이해하기 &amp;mdash; 식당 주방의 4가지 규칙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;규칙 ① I/O는 `async`, CPU 바인드 작업은 `Task.Run`&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;규칙 ② `async void`는 이벤트 핸들러에서만&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;규칙 ③ `await` 앞뒤에서 예외가 어떻게 전파되는지 항상 확인&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;규칙 ④ 공유 상태는 피하고, 불가피하면 `lock` 또는 불변 데이터로 감싸기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;4가지 규칙 위반 자가 진단표&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-8&quot;&gt;PART 13 학습 회고&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-9&quot;&gt;마무리 한 줄&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;TL;DR &amp;mdash; 한눈에 보는 4가지 규칙&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;번호&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;규칙&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;한 줄 요약&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;①&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;I/O는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;, CPU 작업은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;기다리는 일과 일하는 일은 다른 도구를 쓴다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;②&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;는 이벤트 핸들러에서만&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;추적 불가능한 비동기는 앱을 죽인다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;③&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 앞뒤 예외 전파를 항상 확인&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 없는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;는 예외도 없는 셈이다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;④&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;공유 상태는 피하고, 불가피하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 또는 불변 데이터&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;는 스레드 경계를 넘는다는 사실을 잊지 않는다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;신입 시절 비동기 코드를 짜다가 &quot;왜 이 예외가 안 잡히지?&quot;, &quot;왜 앱이 갑자기 죽지?&quot;, &quot;왜 카운터 값이 이상하지?&quot; 같은 의문이 들었다면 거의 100% 이 4가지 중 하나를 어겼기 때문입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;비유로 먼저 이해하기 &amp;mdash; 식당 주방의 4가지 규칙&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기&amp;middot;병렬 프로그래밍을 식당 주방에 비유해 보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주방장(메인 스레드)&lt;/b&gt;: 손님 응대, 플레이팅 등 핵심 작업을 직접 담당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주방 보조(스레드 풀 스레드)&lt;/b&gt;: 무거운 작업을 위임받습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오븐(I/O 장치)&lt;/b&gt;: 음식을 넣어두면 주방 인력 없이 알아서 익습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공유 도마(공유 상태)&lt;/b&gt;: 여러 사람이 동시에 쓰면 사고가 납니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 비유 위에서 4가지 규칙은 다음과 같이 매칭됩니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;I/O는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;, CPU는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;&lt;/b&gt; &amp;mdash; 오븐에 음식을 넣었으면 그냥 다음 일을 하면 됩니다(=&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;). 칼질이 필요하면 보조에게 시킵니다(=&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;). 오븐 앞에 보조를 세워두는 건(=I/O에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;) 인력 낭비입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;는 이벤트 핸들러에서만&lt;/b&gt;&amp;mdash; 보조에게 일을 시켰으면 끝났는지 확인할 수 있어야 합니다. &quot;끝났는지조차 모르는 일&quot;은 손님 콜벨(이벤트 핸들러)에 응답할 때만 허용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 앞뒤 예외 전파&lt;/b&gt;&amp;mdash; 보조가 칼에 베였으면 주방장이 알아야 합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 하지 않으면 보조가 쓰러져도 주방장은 모르고 계속 다음 손님을 받습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공유 상태는 피하고, 어쩔 수 없으면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;&lt;/b&gt; &amp;mdash; 도마 하나를 둘이 동시에 쓰면 손이 잘립니다. 줄을 서거나(=&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt;) 각자 도마를 들고 다니거나(=불변 데이터) 해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비유를 바탕으로 이제 각 규칙을 깊이 들여다보겠습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;규칙 ① I/O는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;, CPU 바인드 작업은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;왜 이 규칙이 필요한가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 코드의 첫 번째 갈림길은 &lt;b&gt;&quot;이 작업이 기다리는 작업인가, 일하는 작업인가&quot;&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;I/O 바인드(I/O-bound)&lt;/b&gt;: 네트워크 호출, 파일 읽기/쓰기, DB 쿼리. CPU는 거의 안 쓰고, 외부 장치의 응답을 기다립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CPU 바인드(CPU-bound)&lt;/b&gt;: 이미지 압축, 경로 탐색, JSON 파싱(대용량). CPU가 계속 일해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async/await&lt;/code&gt;는 &lt;b&gt;스레드를 기다리지 않고 풀어주는 메커니즘&lt;/b&gt;이고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;은 &lt;b&gt;다른 스레드에 일을 위임하는 메커니즘&lt;/b&gt;입니다. 둘은 목적이 완전히 다릅니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/cEig8w/dJMcaaFgfAE/AAAAAAAAAAAAAAAAAAAAAO0UyqJJMvuq_YSebH2YbZ321Q4HvmIJFky1mNDnGzbl/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=TM63tUc37ArNQcbyaisUvlQHidI%3D&quot; alt=&quot;작업의 성격에 따라 도구를 고른다&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;위반 사례&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 안티패턴 1: I/O 작업을 Task.Run으로 이중 포장 &amp;mdash; 스레드 풀 낭비
public Task&amp;lt;string&amp;gt; FetchUserAsync(string id)
{
    var http = new HttpClient();
    return Task.Run(() =&amp;gt; http.GetStringAsync($&quot;/users/{id}&quot;));
    //     ^^^^^^^^^ GetStringAsync는 이미 비동기 I/O다.
    //     Task.Run은 스레드 풀 스레드 1개를 잡아서 await만 시키고 있음.
}

// 안티패턴 2: CPU 작업을 await로만 감쌌지만 사실은 동기
public async Task&amp;lt;int&amp;gt; ComputeChecksumAsync(byte[] data)
{
    int sum = 0;
    for (int i = 0; i &amp;lt; data.Length; i++) sum += data[i] * 31;
    // await 한 번도 없음 &amp;rarr; 호출 스레드가 그대로 점유됨
    // 메인 스레드에서 호출하면 UI 프리징
    return sum;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;올바른 코드&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// I/O 바인드: GetStringAsync 자체가 비동기. Task.Run 불필요
public async Task&amp;lt;string&amp;gt; FetchUserAsync(string id)
{
    using var http = new HttpClient();
    return await http.GetStringAsync($&quot;/users/{id}&quot;);
}

// CPU 바인드: 호출 측에서 Task.Run으로 위임
public Task&amp;lt;int&amp;gt; ComputeChecksumAsync(byte[] data)
{
    return Task.Run(() =&amp;gt;
    {
        int sum = 0;
        for (int i = 0; i &amp;lt; data.Length; i++) sum += data[i] * 31;
        return sum;
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;Unity 모바일 함정&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;모바일은 데스크톱보다 스레드 풀과 배터리 예산이 빠듯합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;I/O에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt; 남발&lt;/b&gt; &amp;rarr; 스레드 풀 고갈(starvation), 배터리 소모 증가.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메인 스레드에서 무거운 연산&lt;/b&gt; &amp;rarr; 프레임 드랍, 스터터링.&lt;/li&gt;
&lt;li&gt;결론: 네트워크는 그대로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;, 무거운 계산만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;으로 넘긴 뒤, &lt;b&gt;결과를 받아 Unity API를 만질 때만&lt;/b&gt; 메인 스레드로 돌아옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;한 줄 요약&lt;/h3&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;기다리는 일은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;, 일하는 일은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;. 둘을 바꿔 쓰면 스레드와 배터리가 모두 손해.&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;규칙 ② &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;는 이벤트 핸들러에서만&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;왜 이 규칙이 필요한가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드의 반환형은 세 가지가 있습니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;반환형&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;추적 가능?&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;예외 잡기&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;일반 비동기 메서드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;결과 반환 비동기 메서드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;❌ (앱 크래시)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;이벤트 핸들러 전용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;는 &lt;b&gt;호출자가 끝났는지 알 수도, 예외를 잡을 수도 없는&lt;/b&gt; &quot;쏘고 잊는(fire-and-forget)&quot; 형태입니다. 예외가 발생하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;에 담기지 않고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt;로 직접 던져져서, 대부분의 경우 &lt;b&gt;프로세스가 즉시 종료&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;유일한 예외는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Button_Click(object, EventArgs)&lt;/code&gt; 같은 UI 이벤트 핸들러입니다. 이벤트 핸들러는 시그니처가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt;로 강제되어 있어서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;로 만들 수밖에 없습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;위반 사례&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;axapta&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class OrderService
{
    // 안티패턴: 일반 메서드를 async void로 선언
    public async void SaveOrderAsync(Order order)
    {
        await _db.InsertAsync(order);
        if (order.Total &amp;lt; 0)
            throw new InvalidOperationException(&quot;Total &amp;lt; 0&quot;);
            // 이 예외는 호출자가 잡을 수 없다.
            // 앱이 그냥 죽는다.
    }
}

// 호출 측 &amp;mdash; 잡으려고 해도 잡히지 않음
public void Process(Order order)
{
    try
    {
        _service.SaveOrderAsync(order); // await 못 함, Task가 없음
    }
    catch (Exception)
    {
        // 절대 실행되지 않음
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;올바른 코드&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class OrderService
{
    // 일반 메서드는 Task 반환
    public async Task SaveOrderAsync(Order order)
    {
        await _db.InsertAsync(order);
        if (order.Total &amp;lt; 0)
            throw new InvalidOperationException(&quot;Total &amp;lt; 0&quot;);
    }
}

// 이벤트 핸들러만 async void 허용
public partial class OrderForm : Form
{
    private async void SaveButton_Click(object sender, EventArgs e)
    {
        try
        {
            await _service.SaveOrderAsync(_currentOrder);
            MessageBox.Show(&quot;저장 완료&quot;);
        }
        catch (InvalidOperationException ex)
        {
            MessageBox.Show($&quot;저장 실패: {ex.Message}&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;IL/상태머신으로 보는 차이&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드는 컴파일러가 빌더를 통해 상태 머신을 만듭니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt; &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&lt;/code&gt; 사용. 예외를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Exception&lt;/code&gt;에 저장하고 상태를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Faulted&lt;/code&gt;로 전환. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 이 예외를 다시 throw 해 줌.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncVoidMethodBuilder&lt;/code&gt; 사용. 예외를 담을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;가 없으므로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext.Post&lt;/code&gt;로 예외를 직접 전송. UI 컨텍스트는 이를 처리되지 않은 예외로 보고 앱을 종료.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// async Task &amp;mdash; 빌더가 예외를 Task에 캡처
[AsyncStateMachine(typeof(StateMachine_Task))]
public Task DoWorkAsync() { /* ... */ }

// async void &amp;mdash; 빌더가 예외를 SynchronizationContext로 던짐
[AsyncStateMachine(typeof(StateMachine_Void))]
public void DoWorkAsync() { /* ... */ }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;Unity 모바일 함정&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빌드된 모바일 앱에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 예외는 &lt;b&gt;로그도 남기지 않고 앱이 종료&lt;/b&gt;되는 경우가 많아 디버깅이 매우 어렵습니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void Start()&lt;/code&gt;는 문법적으로 가능하지만, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Start&lt;/code&gt; 내부에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt; 메서드를 호출하고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt;를 명시적으로 두는 패턴이 훨씬 안전합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Unity에서 안전한 진입 패턴
private void Start()
{
    _ = StartCoreAsync(); // 또는 별도 헬퍼로 예외 로깅
}

private async Task StartCoreAsync()
{
    try
    {
        await LoadAssetsAsync();
        await ConnectToServerAsync();
    }
    catch (Exception ex)
    {
        Debug.LogException(ex); // 최소한 로그는 남는다
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;한 줄 요약&lt;/h3&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;는 이벤트 핸들러 전용. 그 외에는 무조건 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt;로 반환해야 추적&amp;middot;예외 처리가 가능하다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;규칙 ③ &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 앞뒤에서 예외가 어떻게 전파되는지 항상 확인&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;왜 이 규칙이 필요한가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 메서드 내부에서 발생한 예외는 &lt;b&gt;즉시 던져지지 않습니다.&lt;/b&gt; 대신 반환된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;에 캡처됩니다. 이 캡처된 예외를 호출 스택으로 다시 끌어올리는 역할이 바로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/THcLI/dJMcacQzpUe/AAAAAAAAAAAAAAAAAAAAACIUN6cbmVLQSKul55vT1Vv6kYwYC4W8AIwGGPaHEy1M/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=cyKq6oj6HK0ZZrKpPORjyUXeq7c%3D&quot; alt=&quot;예외는 Task에 담겼다가 await에서 풀린다&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;위반 사례&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 안티패턴 1: await 누락 &amp;mdash; 예외가 잡히지 않음
public async Task RunAsync()
{
    try
    {
        FetchAsync(); // await 빠짐
    }
    catch (HttpRequestException)
    {
        // 절대 실행되지 않음
    }
}

// 안티패턴 2: .Result / .Wait()로 동기 차단 &amp;mdash; UI 데드락
public string GetUserSync()
{
    return FetchUserAsync().Result;
    // UI 스레드에서 호출 시 데드락
    // 예외도 AggregateException으로 한 겹 더 감싸짐
}

// 안티패턴 3: WhenAll 결과를 try/catch로만 감쌌지만, 첫 예외만 잡음
public async Task RunAllAsync(List&amp;lt;Task&amp;gt; tasks)
{
    try
    {
        await Task.WhenAll(tasks);
    }
    catch (Exception ex)
    {
        // 여러 Task가 동시에 예외를 던졌을 때
        // 그중 첫 번째 예외만 잡힘 (나머지는 Task.Exception에 그대로 남음)
        Console.WriteLine(ex.Message);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;올바른 코드&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 1) await로 예외를 정상 풀어내기
public async Task RunAsync()
{
    try
    {
        await FetchAsync();
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($&quot;네트워크 오류: {ex.Message}&quot;);
    }
}

// 2) Result/Wait 절대 금지 &amp;mdash; 끝까지 await로 가져가기
public async Task&amp;lt;string&amp;gt; GetUserAsync()
{
    return await FetchUserAsync();
}

// 3) WhenAll의 모든 예외를 보려면 Task의 Exception 직접 확인
public async Task RunAllAsync(List&amp;lt;Task&amp;gt; tasks)
{
    var whenAll = Task.WhenAll(tasks);
    try
    {
        await whenAll;
    }
    catch
    {
        // 모든 예외 수집
        if (whenAll.Exception is { } agg)
        {
            foreach (var inner in agg.Flatten().InnerExceptions)
                Console.WriteLine($&quot;[실패] {inner.Message}&quot;);
        }
        throw;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;IL/상태머신 관점&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 끝나면 컴파일러는 내부적으로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;awaiter.GetResult()&lt;/code&gt;를 호출합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Faulted&lt;/code&gt; 상태라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetResult()&lt;/code&gt;가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt; 안의 첫 번째 예외를 꺼내 다시 throw 합니다. 그래서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 한 코드는 마치 동기 코드처럼 깔끔하게 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/catch&lt;/code&gt;로 잡을 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;반대로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt; 나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Wait()&lt;/code&gt;은 동기 차단이고, 예외를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt;으로 한 겹 더 감싸서 던지므로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;catch (HttpRequestException)&lt;/code&gt; 같은 구체 타입 잡기가 더 어려워집니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;Unity 모바일 함정&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 메인 스레드에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt;나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Wait()&lt;/code&gt;을 쓰면 &lt;b&gt;데드락&lt;/b&gt;이 거의 확정입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt;가 메인 스레드 1개로 구성되어 있는데, 메인 스레드가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Result&lt;/code&gt;로 자기 자신을 기다리기 때문입니다. 모바일에서 흔한 &quot;앱이 멈췄어요(ANR)&quot; 신고의 큰 비중이 이 패턴입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;한 줄 요약&lt;/h3&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;비동기 메서드를 호출했으면 결과에 관심이 있는 한 반드시 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Wait()&lt;/code&gt;은 데드락 폭탄이다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;규칙 ④ 공유 상태는 피하고, 불가피하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 또는 불변 데이터로 감싸기&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;왜 이 규칙이 필요한가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;는 단순히 &quot;기다리기&quot;가 아니라 &lt;b&gt;&quot;이 지점에서 메서드를 잘랐다가 나중에 다른 스레드에서 이어붙일 수 있는 분기점&quot;&lt;/b&gt; 입니다. 이 사실은 두 가지 함의를 가집니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 앞뒤에서 스레드가 바뀔 수 있다 &amp;rarr; 스레드 친화성(thread-affinity)을 가정하면 안 된다.&lt;/li&gt;
&lt;li&gt;여러 비동기 작업이 같은 변수를 만지면 &lt;b&gt;경쟁 조건(race condition)&lt;/b&gt; 이 생긴다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 가장 자주 빠지는 함정은 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 블록 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;를 쓰려고 하는 것&lt;/b&gt; 입니다. C# 컴파일러는 이를 &lt;b&gt;컴파일 에러&lt;/b&gt;로 막습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;CS1996: Cannot await in the body of a lock statement&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이유는 명확합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;은 IL에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt;로 컴파일되며 &lt;b&gt;락을 획득한 스레드가 직접 해제해야&lt;/b&gt; 합니다. 그런데 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 이후 코드는 다른 스레드에서 실행될 수 있어서 &quot;들어간 스레드 &amp;ne; 나오는 스레드&quot;가 되어 락이 망가집니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;위반 사례&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;private int _counter = 0;
private readonly object _gate = new();

// 안티패턴 1: lock 없이 공유 상태 수정
public async Task UnsafeIncrementAsync()
{
    int t = _counter;
    await Task.Delay(10);  // 여기서 다른 Task가 끼어들 수 있음
    _counter = t + 1;       // &amp;rarr; 카운트 누락
}

// 안티패턴 2: lock 안에서 await &amp;mdash; 컴파일 에러
public async Task InvalidLockAsync()
{
    lock (_gate)
    {
        await Task.Delay(10); // CS1996
        _counter++;
    }
}

// 안티패턴 3: ConcurrentDictionary로 안심하다가 &quot;Read-Modify-Write&quot; 실수
private ConcurrentDictionary&amp;lt;string, int&amp;gt; _scores = new();
public void AddScore(string user, int delta)
{
    var current = _scores.GetValueOrDefault(user, 0); // 1) 읽기
    _scores[user] = current + delta;                   // 2) 쓰기
    // 1과 2 사이에 다른 스레드가 끼어들면 값 손실
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;올바른 코드&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 1) 비동기 안에서 락이 필요하면 SemaphoreSlim
private readonly SemaphoreSlim _gate = new(1, 1);
private int _counter = 0;

public async Task SafeIncrementAsync()
{
    await _gate.WaitAsync();
    try
    {
        int t = _counter;
        await Task.Delay(10);
        _counter = t + 1;
    }
    finally
    {
        _gate.Release();
    }
}

// 2) 단순 카운터는 Interlocked로 락-프리
private long _hits;
public void IncrementHits() =&amp;gt; Interlocked.Increment(ref _hits);

// 3) ConcurrentDictionary는 AddOrUpdate로 원자적 수정
private ConcurrentDictionary&amp;lt;string, int&amp;gt; _scores = new();
public void AddScore(string user, int delta)
{
    _scores.AddOrUpdate(user, delta, (_, prev) =&amp;gt; prev + delta);
}

// 4) 가장 안전한 길 &amp;mdash; 불변 데이터로 만들기
public sealed record GameState(int Score, int Lives, ImmutableList&amp;lt;Item&amp;gt; Items);

// 상태를 바꿀 때마다 새 객체를 만들고 참조만 교체
private GameState _state = new(0, 3, ImmutableList&amp;lt;Item&amp;gt;.Empty);
public void AddItem(Item item)
{
    var snapshot = _state;
    var next = snapshot with { Items = snapshot.Items.Add(item) };
    Interlocked.CompareExchange(ref _state, next, snapshot);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;IL 관점에서 보는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;의 한계&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 원본
lock (_gate) { Critical(); }

// 컴파일러가 생성하는 IL 의사코드
bool taken = false;
try
{
    Monitor.Enter(_gate, ref taken);
    Critical();
}
finally
{
    if (taken) Monitor.Exit(_gate);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt;는 호출 스레드의 ID를 기록합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt;는 같은 스레드에서 호출되어야 합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 이후 다른 스레드가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt;을 호출하려고 하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationLockException&lt;/code&gt;이 발생합니다. 컴파일러는 이 사고를 미연에 방지하기 위해 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock { ... await ... }&lt;/code&gt;를 아예 컴파일 에러로 막습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;Unity 모바일 함정&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Unity의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GameObject&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Transform&lt;/code&gt; 등은 메인 스레드에서만 접근 가능합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 이후에 백그라운드 스레드에서 깨어났다면 Unity API를 만지기 전에 &lt;b&gt;메인 스레드 디스패처&lt;/b&gt;로 복귀해야 합니다(예: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask.SwitchToMainThread()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;모바일은 GC 스파이크에 매우 민감합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 객체 자체도 힙 할당이므로, 빈번한 비동기 호출이 GC를 흔든다면 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt;&lt;/b&gt; 사용을 검토하세요. 단, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt;는 한 번만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 가능하다는 제약이 있어 일반 메서드 반환형을 무조건 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt;로 바꾸는 것은 위험합니다.&lt;/li&gt;
&lt;li&gt;공유 상태는 가능한 한 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ScriptableObject&lt;/code&gt;나 단방향 이벤트(예: 메시지 버스)로 분리하고, 멀티스레드 접근이 정말 필요한 부분만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt;으로 보호합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;한 줄 요약&lt;/h3&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;는 스레드 경계를 넘는다. 공유 상태가 있다면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;을 떠올리고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock { await ... }&lt;/code&gt;은 절대 안 된다는 것만 기억하면 충분하다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;4가지 규칙 위반 자가 진단표&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;작성한 코드를 커밋하기 전에 이 표를 한 번씩 훑어 보세요.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;체크&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;위반 신호&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;대응&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run(() =&amp;gt; httpClient.XxxAsync(...))&lt;/code&gt; 패턴이 있다&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;I/O는 직접 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;UI 응답 중 멈춤이 있다&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;무거운 연산은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;으로 위임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;메서드 반환형이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;이고 시그니처가 이벤트 핸들러가 아니다&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt;로 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/catch&lt;/code&gt;가 있는데 안에서 비동기 메서드를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 없이 호출한다&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Wait()&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetAwaiter().GetResult()&lt;/code&gt; 사용&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;호출 체인을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;로 통일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock (_gate) { ... await ... }&lt;/code&gt; 같은 코드가 컴파일 에러로 보인다&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim.WaitAsync&lt;/code&gt; 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_counter++&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;dict[k] = dict[k] + 1&lt;/code&gt; 같은 read-modify-write 패턴&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AddOrUpdate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;UI 이벤트 핸들러 안에서 비동기 메서드를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 없이 호출&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;이벤트 핸들러는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 자체로 두고 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 8 ===================== --&gt;
&lt;h2 id=&quot;section-8&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;PART 13 학습 회고&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;PART 13에서는 비동기와 스레딩 기초를 14개 주제에 걸쳐 다뤘습니다. 다시 한번 짚어 보면 다음 흐름이었습니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;개념의 전환&lt;/b&gt; (01~04) &amp;mdash; &quot;동기는 한 줄씩 처리, 비동기는 흐름이 갈라진다&quot;는 사고 방식. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Thread&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThreadPool&lt;/code&gt;이라는 OS 추상화 위에서 .NET의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;가 어떻게 만들어졌는지.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Task와 async/await&lt;/b&gt; (05~07) &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;는 결과를 담는 그릇이고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async/await&lt;/code&gt;는 그 그릇을 펴서 동기처럼 쓰게 해주는 문법 설탕. 반환형의 의미.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조합과 취소&lt;/b&gt; (08~09) &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;로 여러 작업을 묶고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;으로 협조적 취소를 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예외와 스트림&lt;/b&gt; (10~11) &amp;mdash; 비동기에서 예외는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;에 담긴 보따리. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&lt;/code&gt;은 끝없이 흘러오는 데이터에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;foreach&lt;/code&gt;처럼 접근하게 해 준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동시성 제어&lt;/b&gt; (12~14) &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;, 새 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt;, 그리고 결국 만나게 되는 경쟁 조건과 데드락의 본질.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 모든 내용을 신입이 코드를 짤 때 매번 떠올리기는 어렵습니다. 그래서 마지막 정리편에서 &lt;b&gt;4가지 규칙&lt;/b&gt;으로 압축했습니다. 처음 비동기를 만났을 때 이 4가지만 머릿속에 박아 두면 90%의 사고는 막을 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기&amp;middot;병렬은 한 번에 마스터되는 영역이 아닙니다. 코드를 짜고, 디버깅하고, 운영 환경에서 데드락을 한두 번 겪고 나면 점점 몸에 붙습니다. 이 시리즈가 그 첫걸음의 디딤돌이 되었기를 바랍니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 9 ===================== --&gt;
&lt;h2 id=&quot;section-9&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;마무리 한 줄&lt;/h2&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&quot;기다리는 일과 일하는 일을 구분하고, 끝났는지 알 수 있게 만들고, 예외는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;로 받아 내고, 공유 상태는 가능한 한 만들지 않는다.&quot;&lt;/b&gt; &amp;mdash; 이 한 문장이 PART 13 전체의 결론이자, 비동기 코드를 짤 때 매일 자신에게 거는 4중 안전벨트입니다.&lt;/blockquote&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>C#</category>
      <category>기초</category>
      <category>입문</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/585</guid>
      <comments>https://everyday-devup.tistory.com/585#entry585comment</comments>
      <pubDate>Sat, 9 May 2026 01:10:16 +0900</pubDate>
    </item>
    <item>
      <title>[PART13.비동기와 스레딩 기초(14/15)] 경쟁 상태와 데드락 &amp;mdash; 감 잡기</title>
      <link>https://everyday-devup.tistory.com/584</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(14/15)] 경쟁 상태와 데드락 &amp;mdash; 감 잡기&lt;/h1&gt;
&lt;p style=&quot;color: #868e96; font-size: 14px; margin: 0 0 32px; line-height: 1.6;&quot; data-ke-size=&quot;size16&quot;&gt;멀티스레딩 코드를 처음 짜면 &quot;분명 1000번 더했는데 결과가 987이 나오고&quot;, &quot;잘 돌던 프로그램이 어느 날 갑자기 멈춰서 안 풀린다.&quot; 두 현상은 각각 &lt;b&gt;경쟁 상태(Race Condition)&lt;/b&gt; 와 &lt;b&gt;데드락(Deadlock)&lt;/b&gt; 입니다. 이 글은 두 버그가 *왜* 생기는지 그림으로 감을 잡고, *최소한의 도구* (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt;) 로 막는 법, 그리고 *피하는 코딩 습관* 까지 입문자 시각으로 정리합니다. ThreadSanitizer 같은 탐지 도구&amp;middot;재진입 락&amp;middot;메모리 배리어는 심화 커리큘럼에서 다룹니다.&lt;/p&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;한 줄 요약&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;왜 멀티스레딩에서 결과가 어긋날까&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;비유로 잡기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;코드로 감 잡기 &amp;mdash; 경쟁 상태 재현과 방어&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;데드락 &amp;mdash; 멈춰서 안 풀리는 상태&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;예방 규칙 세 가지 &amp;mdash; &quot;감 잡기&quot; 단계에서 이것만 지켜도 80%&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;Unity 입문자가 만나기 쉬운 함정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-8&quot;&gt;한눈 비교 &amp;mdash; `Interlocked` vs `lock`&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-9&quot;&gt;심화 커리큘럼 (이 글의 범위 밖)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-10&quot;&gt;정리&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;0. 한 줄 요약&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;경쟁 상태&lt;/b&gt;: 두 스레드가 같은 변수를 동시에 읽고 쓸 때, *실행 순서가 매번 달라서* 결과도 매번 달라지는 버그입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데드락&lt;/b&gt;: 두 스레드가 서로 *상대방이 들고 있는 락을 기다리느라* 영원히 멈춰버리는 상태입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;방어 도구&lt;/b&gt;: 단순 카운터는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt;, 여러 줄짜리 임계 영역은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;. 둘 다 &quot;한 번에 하나만 들어가게&quot; 만들어 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데드락 회피 규칙&lt;/b&gt;: 락 잡는 순서를 *모든 코드에서 동일하게*, 락 안에서는 *외부 코드 호출 금지*, 락 범위는 *최대한 짧게*.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;1. 왜 멀티스레딩에서 결과가 어긋날까&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;1-1. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;counter++&lt;/code&gt; 는 한 줄이 아닙니다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 코드를 작성하는 사람의 눈에는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;counter++&lt;/code&gt; 가 한 동작으로 보입니다. 그런데 CPU 입장에서는 세 단계입니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Read&lt;/b&gt; &amp;mdash; 메모리에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;counter&lt;/code&gt; 값을 레지스터로 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Modify&lt;/b&gt; &amp;mdash; 레지스터에서 1을 더합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Write&lt;/b&gt; &amp;mdash; 레지스터의 새 값을 메모리에 다시 씁니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 세 단계 사이 *어디든* 다른 스레드가 끼어들 수 있습니다. 끼어들면 한 번 증가가 통째로 사라집니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/bDvccW/dJMcahYD8Kf/AAAAAAAAAAAAAAAAAAAAAP3ikbGDbt3aWTcP3Z77z79M6K8Mi8AVxO4vr7jXAnyf/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=HZNEdTJMnAByEkTt5%2Bca2SrggUU%3D&quot; alt=&quot;counter++ 가 두 스레드에서 겹치면&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;A 가 10을 읽은 직후 B 가 끼어들어 똑같이 10을 읽고, 둘 다 11을 씁니다. 두 번 더했는데 한 번분만 적용됐습니다. 이게 &lt;b&gt;잃어버린 갱신(Lost Update)&lt;/b&gt; 으로 부르는 가장 흔한 경쟁 상태입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;1-2. 컴파일러&amp;middot;CPU 의 명령 재배치&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;JIT 컴파일러와 CPU 는 *단일 스레드 결과만 같다면* 명령 순서를 자유롭게 바꿔도 된다는 규칙으로 최적화합니다. 단일 스레드에선 문제가 없지만, 다른 스레드가 그 변수를 *중간 상태* 로 관찰할 때 의도가 깨집니다. 이 글에서는 &quot;이런 것도 있다&quot; 정도로만 짚고, 본격적인 해결(메모리 배리어, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;volatile&lt;/code&gt;)은 *심화 커리큘럼* 에서 다룹니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;용어 짧게&lt;/b&gt; &amp;mdash; *원자적(atomic) 연산*: 중간에 끊기지 않고 한 번에 끝나는 연산입니다. CPU 가 직접 보장합니다. *임계 영역(critical section)*: 한 번에 한 스레드만 들어가야 안전한 코드 구간입니다.&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;2. 비유로 잡기&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;은행 ATM 두 대가 같은 통장 잔고에 동시에 접근한다고 상상합니다. 두 사람이 동시에 1만 원씩 입금하는데, 두 ATM 모두 *입금 직전 잔고* 를 따로 읽어서 1만 원을 더하고 *각자* 결과를 통장에 씁니다. 한쪽이 덮어써서 1만 원이 사라집니다 &amp;mdash; &lt;b&gt;경쟁 상태&lt;/b&gt;.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이번엔 두 사람이 *각자* 다른 ATM 에서 작업 중인데, 첫 번째 사람은 통장 A 의 인증을 잡은 채 통장 B 의 인증을 기다리고, 두 번째 사람은 통장 B 의 인증을 잡은 채 통장 A 의 인증을 기다립니다. 둘 다 손에 든 자원을 놓지 못한 채 영원히 기다립니다 &amp;mdash; &lt;b&gt;데드락&lt;/b&gt;.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;차이를 한 줄로:&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경쟁 상태 &amp;rarr; &quot;결과가 *틀려진다*.&quot;&lt;/li&gt;
&lt;li&gt;데드락 &amp;rarr; &quot;결과를 못 내고 *멈춘다*.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;3. 코드로 감 잡기 &amp;mdash; 경쟁 상태 재현과 방어&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3-1. 가장 작은 재현 코드&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

class RaceDemo
{
    static int counter = 0;

    static void Main()
    {
        // 두 스레드가 각각 100,000번씩 ++ &amp;rarr; 기대값 200,000
        Parallel.Invoke(
            () =&amp;gt; { for (int i = 0; i &amp;lt; 100_000; i++) counter++; },
            () =&amp;gt; { for (int i = 0; i &amp;lt; 100_000; i++) counter++; }
        );

        Console.WriteLine($&quot;실제: {counter} / 기대: 200,000&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드를 여러 번 돌리면 결과가 &lt;b&gt;매번 다릅니다.&lt;/b&gt; 어떤 때는 198,742, 어떤 때는 173,005. 200,000 이 *우연히* 나올 수도 있습니다. 멀티스레드 버그는 이렇게 *재현이 들쑥날쑥하다는 것* 자체가 가장 골치 아픈 특징입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3-2. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt; &amp;mdash; 단순 연산의 가벼운 답&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;counter++&lt;/code&gt; 정도의 *한 변수에 대한 단순 연산* 은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt; 가 제일 빠릅니다. CPU 가 직접 지원하는 원자적 명령(예: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;LOCK XADD&lt;/code&gt;) 으로 컴파일되어, 락 획득&amp;middot;해제의 OS 비용이 들지 않습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;static int counter = 0;

Parallel.Invoke(
    () =&amp;gt; { for (int i = 0; i &amp;lt; 100_000; i++) Interlocked.Increment(ref counter); },
    () =&amp;gt; { for (int i = 0; i &amp;lt; 100_000; i++) Interlocked.Increment(ref counter); }
);

Console.WriteLine($&quot;실제: {counter}&quot;); // 항상 200,000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;세 가지만 기억하면 충분합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;메서드&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.Increment(ref x)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;x++&lt;/code&gt; 를 원자적으로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.Add(ref x, n)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;x += n&lt;/code&gt; 를 원자적으로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.CompareExchange(ref x, newValue, expected)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;x&lt;/code&gt; 가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;expected&lt;/code&gt; 와 같을 때만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;newValue&lt;/code&gt; 로 바꿉니다. 락 없이 동시성 자료구조를 만드는 핵심 도구입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3-3. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; &amp;mdash; 여러 줄을 묶어야 할 때&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;상태가 여러 변수에 걸쳐 있어 *동시에 일관* 돼야 한다면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 으로 묶습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;class Wallet
{
    private readonly object _gate = new();      // 락 전용 객체
    private int _krw;
    private int _coin;

    public void Exchange(int krwIn, int coinOut)
    {
        // 두 변수의 변경이 &quot;동시에&quot; 일어난 것처럼 보여야 합니다.
        lock (_gate)
        {
            _krw -= krwIn;
            _coin += coinOut;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(obj) { ... }&lt;/code&gt; 은 컴파일러가 다음 패턴으로 풀어줍니다(개념적 형태).&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;sqf&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;bool taken = false;
try
{
    Monitor.Enter(_gate, ref taken);
    _krw -= krwIn;
    _coin += coinOut;
}
finally
{
    if (taken) Monitor.Exit(_gate);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 두 가지입니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt; 가 &lt;b&gt;상호 배제&lt;/b&gt; 를 만듭니다. 다른 스레드가 이미 들어가 있으면 풀릴 때까지 기다립니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/finally&lt;/code&gt; 로 &lt;b&gt;예외가 터져도 락은 반드시 풀립니다.&lt;/b&gt; 락이 풀리지 않으면 그 자체가 또 다른 정지 원인이 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;락 객체 고르는 규칙&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly object _gate = new();&lt;/code&gt; 같은 *전용 객체* 를 만들어 잠급니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;this&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;typeof(MyClass)&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;string&lt;/code&gt; 리터럴 같은 *외부에 노출된 참조* 는 다른 코드가 같은 객체로 락을 잡아 의도치 않은 데드락을 만들 수 있어 피합니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3-4. IL 로 한 번 들여다보기 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 은 정말 try/finally 일까&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 13 이전 컴파일러로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(_gate) { ... }&lt;/code&gt; 을 컴파일한 IL 은 다음과 같습니다(요지만 발췌).&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #f1f3f5; color: #495057; padding: 16px 20px; border-radius: 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0 0 16px;&quot;&gt;&lt;code&gt;ldarg.0
ldfld      object Wallet::_gate
stloc.0
ldc.i4.0
stloc.1
.try
{
  ldloc.0
  ldloca.s   1
  call       void [System.Threading]Monitor::Enter(object, bool&amp;amp;)
  // ...임계 영역 본문...
  leave.s    END
}
finally
{
  ldloc.1
  brfalse.s  SKIP
  ldloc.0
  call       void [System.Threading]Monitor::Exit(object)
  SKIP: endfinally
}
END: ret&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;읽기 포인트:&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter(obj, ref taken)&lt;/code&gt; 와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit(obj)&lt;/code&gt; 가 그대로 보입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드는 &lt;b&gt;문법적 설탕&lt;/b&gt; 입니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;taken&lt;/code&gt; 변수가 핵심입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enter&lt;/code&gt; 가 락을 *실제로 잡았는지* 가 기록되고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally&lt;/code&gt; 에서 잡힌 경우에만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt; 합니다. 락을 못 잡은 채로 예외가 나면 잘못된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt; 를 호출하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.Increment&lt;/code&gt; 는 IL 에서 일반 메서드 호출처럼 보이지만, JIT 가 플랫폼별 *원자적 명령* (x86: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;LOCK XADD&lt;/code&gt;, ARM: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;LDADD&lt;/code&gt;) 으로 인라인화합니다. 그래서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 보다 압도적으로 빠른 겁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 13 이상에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt; 타입을 도입하면서 IL 패턴이 약간 달라지지만, &quot;Enter/Exit 를 try/finally 로 감싼다&quot;는 본질은 동일합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;4. 데드락 &amp;mdash; 멈춰서 안 풀리는 상태&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4-1. 데드락의 4가지 필요 조건 (Coffman 조건)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;네 조건이 *동시에* 충족돼야 데드락이 일어납니다. 거꾸로, *하나만 깨도* 데드락은 사라집니다. 예방 규칙은 모두 이 네 가지를 깨는 방법입니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;조건&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;의미&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;깨는 법&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;상호 배제&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;자원은 한 번에 한 스레드만 점유&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;(락의 본질이라 깨기 어려움)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;점유와 대기&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;자원을 들고 다른 자원을 기다림&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;필요한 락을 *한꺼번에* 잡거나 기다리는 동안 들고 있던 락을 놓는다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;비선점&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;들고 있는 락을 빼앗을 수 없음&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.TryEnter&lt;/code&gt; 처럼 *타임아웃&amp;middot;실패* 가능한 API 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;순환 대기&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;A&amp;rarr;B, B&amp;rarr;A 로 사이클이 만들어짐&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;락 획득 순서를 전역적으로 통일&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4-2. 가장 작은 데드락 재현 코드&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Threading;

class DeadlockDemo
{
    static readonly object lockA = new();
    static readonly object lockB = new();

    static void Main()
    {
        var t1 = new Thread(() =&amp;gt;
        {
            lock (lockA)
            {
                Thread.Sleep(50);          // 상대가 lockB 를 잡을 시간 확보
                lock (lockB) { /* unreached */ }
            }
        });

        var t2 = new Thread(() =&amp;gt;
        {
            lock (lockB)
            {
                Thread.Sleep(50);
                lock (lockA) { /* unreached */ }
            }
        });

        t1.Start(); t2.Start();
        t1.Join();  t2.Join();              // 영원히 안 끝남
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4-3. 그림으로 보는 순환 대기&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/rhd2O/dJMcaaSMDe8/AAAAAAAAAAAAAAAAAAAAAP-wB6F13EtNvRcX_8MLnqHyVn4jWnUvRgUWecimlFtY/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=m%2B80ZDbEuR91LsnGTj%2BgAAI%2BSjo%3D&quot; alt=&quot;순환 대기 &amp;mdash; 두 스레드가 서로의 락을 기다림&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4-4. 한 줄만 고치면 풀리는 이유&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드를 두 스레드 모두 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lockA &amp;rarr; lockB&lt;/code&gt; 순서로 통일&lt;/b&gt; 하면 데드락이 사라집니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arcade&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// t2 의 락 순서를 t1 과 같게 맞춤
var t2 = new Thread(() =&amp;gt;
{
    lock (lockA)        // 먼저 lockA
    {
        Thread.Sleep(50);
        lock (lockB) { /* ok */ }
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;A 를 먼저 잡은 한 스레드만 진행하고, 나머지는 *A 부터* 차분히 기다립니다. 순환이 안 만들어집니다 &amp;mdash; Coffman 조건 4(순환 대기)가 깨졌습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;5. 예방 규칙 세 가지 &amp;mdash; &quot;감 잡기&quot; 단계에서 이것만 지켜도 80%&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;규칙 1. 락 획득 순서를 *전역적으로* 같게 유지한다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;여러 락을 함께 잡아야 한다면, *어떤 코드 경로에서든* 같은 순서로 잡습니다. 객체에 정렬 가능한 식별자(예: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;RuntimeHelpers.GetHashCode&lt;/code&gt;, ID) 가 있으면 작은 쪽 먼저 잡는 식으로 *기계적으로* 정합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;pgsql&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 두 계좌를 동시에 잠가야 할 때 &amp;mdash; ID 작은 쪽부터
public static void Transfer(Account from, Account to, int amount)
{
    var (first, second) = from.Id &amp;lt; to.Id ? (from, to) : (to, from);
    lock (first.Gate)
    lock (second.Gate)
    {
        from.Balance -= amount;
        to.Balance   += amount;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;규칙 2. 락 안에서 *외부* 코드를 호출하지 않는다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이벤트 발행, 콜백, 가상 메서드, 외부 라이브러리 호출은 *어떤 락을 추가로 잡을지 알 수 없습니다.* 락을 쥔 채로 그런 코드를 부르면 의도치 않은 순환이 만들어집니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ 위험 &amp;mdash; 락 안에서 콜백 호출
lock (_gate)
{
    _items.Add(item);
    OnItemAdded?.Invoke(item);   // 구독자가 또 다른 락을 잡으면? 데드락 가능성
}

// ✅ 안전 &amp;mdash; 락 밖으로 빼고 발행
ItemAddedEvent? toRaise = null;
lock (_gate)
{
    _items.Add(item);
    toRaise = OnItemAdded;
}
toRaise?.Invoke(item);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;규칙 3. 락 범위는 *최대한 짧게*&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;락을 들고 있는 시간이 길수록 다른 스레드가 굶고, 데드락이 만들어질 수 있는 *틈* 이 늘어납니다. 락 안에서는 *공유 상태 변경* 만 하고, 계산&amp;middot;I/O&amp;middot;로깅은 락 밖에서 합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ 락 안에서 무거운 작업
lock (_gate)
{
    var json = JsonSerializer.Serialize(_state);   // CPU 무거움
    File.WriteAllText(&quot;dump.json&quot;, json);          // I/O &amp;mdash; 절대 락 안에서 X
}

// ✅ 스냅샷만 락 안에서 만들고 락 밖에서 처리
State snapshot;
lock (_gate)
{
    snapshot = _state.Clone();
}
var json = JsonSerializer.Serialize(snapshot);
File.WriteAllText(&quot;dump.json&quot;, json);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;6. Unity 입문자가 만나기 쉬운 함정&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 게임 로직은 대부분 *메인 스레드* 에서 돌아갑니다. 그래서 입문자는 &quot;나는 멀티스레딩 안 쓰는데&quot;라고 생각하기 쉽습니다. 그러나 다음 상황은 *모르는 사이* 다른 스레드를 끌어옵니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;&lt;/b&gt; &amp;mdash; 무거운 계산을 백그라운드로 보낸 뒤, 그 결과를 메인 스레드의 게임 상태에 반영할 때 둘 사이 공유 변수에 경쟁 상태가 생깁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async/await&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(false)&lt;/code&gt;&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 이후 코드가 *백그라운드* 스레드에서 이어지는데, 그 코드에서 Unity API 를 부르면 충돌합니다. (Unity API 는 거의 다 *메인 스레드 전용* 입니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;C# Job System / Burst&lt;/b&gt; &amp;mdash; 프레임워크 차원에서 안전 검사를 강제하지만, *static* 필드나 외부 컬렉션을 만지면 여전히 경쟁 상태가 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 콜백&lt;/b&gt; &amp;mdash; 네트워크 라이브러리(예: 일부 소켓&amp;middot;SDK) 콜백이 *백그라운드 스레드* 에서 호출됩니다. 거기서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GameObject&lt;/code&gt; 를 직접 만지면 즉시 예외 또는 더 미묘한 깨짐이 생깁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;현실적인 입문자 가이드라인&lt;/b&gt; &amp;mdash; 백그라운드 스레드에서는 *데이터만 계산해서 큐로 메인 스레드에 넘기고*, Unity API 는 항상 메인 스레드에서 호출합니다. 공유하는 단순 카운터&amp;middot;플래그는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt; 로 막고, 그 이상 복잡한 상태는 메시지 큐(예: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code&gt;) 한 곳을 두고 *한 스레드만 쓰기* 패턴으로 잡으면 대부분의 사고를 피할 수 있습니다.&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 8 ===================== --&gt;
&lt;h2 id=&quot;section-8&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;7. 한눈 비교 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt; vs &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt;&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;보호 범위&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;단일 변수, 한 연산&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;여러 줄&amp;middot;여러 변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;비용&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;매우 저렴 (CPU 명령 1~2개 수준)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;상대적으로 비쌈 (경쟁 시 OS 대기)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;데드락 가능성&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;거의 없음 (단일 연산이라 사이클 없음)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;*순서 통일 안 하면* 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;적합한 예&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;카운터, 플래그, 시퀀스 ID 발급&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;컬렉션 변경, 두 필드 동시 갱신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;함정&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;int&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;long&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref&lt;/code&gt; 타입 위주, 복합 로직 표현 한계&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 객체 잘못 고르거나 외부 호출 들이는 실수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;원칙 &amp;mdash; &lt;b&gt;단일 변수는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt;, 그 외는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;.&lt;/b&gt; 락이 꼭 필요한 상황에서만 락을 씁니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 9 ===================== --&gt;
&lt;h2 id=&quot;section-9&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;심화 커리큘럼 (이 글의 범위 밖)&lt;/h2&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;본격적으로 멀티스레딩 디버깅&amp;middot;튜닝에 들어가면 다음 주제가 따라옵니다. 이 시리즈에서는 별도 글로 다룹니다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;
&lt;ul style=&quot;margin: 8px 0; padding-left: 20px;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 모델&amp;middot;메모리 배리어&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;volatile&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Volatile.Read/Write&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.MemoryBarrier&lt;/code&gt; &amp;mdash; JIT/CPU 재배치를 *언제* 막아야 하는가.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재진입 락(Reentrant Lock)&lt;/b&gt;: 같은 스레드가 같은 락을 다시 잡을 때의 동작과 함정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ReaderWriterLockSlim&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt;&lt;/b&gt; 같은 추가 동기화 프리미티브.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Lock-free 자료구조&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CompareExchange&lt;/code&gt; 기반의 큐&amp;middot;스택&amp;middot;해시맵 설계.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;탐지 도구&lt;/b&gt;: ThreadSanitizer (Linux/Clang), Visual Studio Concurrency Visualizer, BenchmarkDotNet 의 동시성 모드, Unity Burst 의 aliasing 검사.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Async 동기화 컨텍스트와 데드락&lt;/b&gt;: WinForms/WPF/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt; 위에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Wait()&lt;/code&gt; 가 만드는 클래식 데드락.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 10 ===================== --&gt;
&lt;h2 id=&quot;section-10&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;8. 정리&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티스레드 코드의 두 대표 버그: 결과가 *틀려지는* 경쟁 상태, 결과 없이 *멈추는* 데드락.&lt;/li&gt;
&lt;li&gt;경쟁 상태의 뿌리는 *비원자적 연산* 과 *재배치*. 단일 변수는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt; 로, 여러 줄은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 으로 막습니다.&lt;/li&gt;
&lt;li&gt;데드락은 *순환 대기* 가 핵심 &amp;mdash; 락 순서 통일, 락 안에서 외부 호출 금지, 락 범위 최소화 세 규칙으로 거의 막습니다.&lt;/li&gt;
&lt;li&gt;Unity 메인 스레드 환경이라도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async/await&lt;/code&gt;&amp;middot;콜백&amp;middot;Job System 사용 시 동일한 규칙이 그대로 적용됩니다.&lt;/li&gt;
&lt;li&gt;더 깊은 메모리 모델&amp;middot;탐지 도구&amp;middot;Lock-free 설계는 심화 커리큘럼에서 이어 갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>C#</category>
      <category>기초</category>
      <category>입문</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/584</guid>
      <comments>https://everyday-devup.tistory.com/584#entry584comment</comments>
      <pubDate>Sat, 9 May 2026 01:01:09 +0900</pubDate>
    </item>
    <item>
      <title>[PART13.비동기와 스레딩 기초(13/15)] System.Threading.Lock &amp;mdash; .NET 9의 새 락 타입 (C# 13)</title>
      <link>https://everyday-devup.tistory.com/583</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(13/15)] System.Threading.Lock &amp;mdash; .NET 9의 새 락 타입 (C# 13)&lt;/h1&gt;
&lt;p style=&quot;color: #868e96; font-size: 14px; margin: 0 0 32px; line-height: 1.6;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 글에서 다루는 것&lt;/b&gt; - C# 13 / .NET 9 에서 새로 추가된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt; 타입이 왜 도입되었는가 - 컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드를 만났을 때 락 대상의 타입에 따라 어떻게 다른 코드로 변환하는가 - &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;EnterScope()&lt;/code&gt; 가 반환하는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct Scope&lt;/code&gt; 의 의미와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 패턴 - &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 객체를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 로 캐스팅하면 왜 새 API 가 동작하지 않는가 (CS9216 경고) - 기존 코드를 안전하게 마이그레이션하는 한 줄 변경법&lt;/p&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;왜 새 타입이 필요했는가 &amp;mdash; `lock(object)` 의 구조적 부담&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;30초 요약 &amp;mdash; 한 줄만 바꾸면 됩니다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;ADEPT &amp;mdash; 비유로 이해하는 `Lock` vs `lock(object)`&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;IL 로 직접 확인하기 &amp;mdash; 두 코드는 정말 다른 IL 로 컴파일됩니다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;`Lock.EnterScope()` 와 `ref struct Scope` &amp;mdash; 왜 굳이 ref struct 인가&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;함정 &amp;mdash; `object` 캐스팅&amp;middot;할당 시 새 API 가 사라진다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;`using` 패턴으로 명시적으로 쓰기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-8&quot;&gt;`TryEnter` &amp;mdash; 시간 제한이 있는 락 시도&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-9&quot;&gt;마이그레이션 가이드 &amp;mdash; 점진적으로 옮기기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-10&quot;&gt;Unity 환경에서의 주의&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-11&quot;&gt;자주 묻는 질문&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-12&quot;&gt;정리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-13&quot;&gt;참고 자료&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;blockquote style=&quot;background: #ebfbee; border-left: 4px solid #40c057; border-radius: 0 8px 8px 0; padding: 14px 18px; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;앞 주제와의 관계&lt;/b&gt; 이전 글([12. lock 문](../12.%20lock%20문))에서는 고전 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(_syncRoot)&lt;/code&gt; 패턴이 내부적으로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt; 로 변환되는 과정을 IL 수준까지 들여다봤습니다. 이 글은 그 다음 단계입니다 &amp;mdash; &lt;b&gt;같은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드를 그대로 쓰면서도, 락 객체의 타입만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 으로 바꿨을 때 컴파일러가 완전히 다른 IL 을 생성한다&lt;/b&gt;는 사실을 보여주는 것이 핵심입니다.&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;1. 왜 새 타입이 필요했는가 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(object)&lt;/code&gt; 의 구조적 부담&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(_syncRoot)&lt;/code&gt; 한 줄을 쓰는 순간 런타임은 다음 세 가지를 떠안습니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;모든 참조 객체가 락이 될 수 있다는 일반성&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter(object)&lt;/code&gt; 는 인자 타입이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 입니다. 이 일반성을 지원하기 위해 CLR 은 힙에 있는 &lt;b&gt;모든&lt;/b&gt; 객체에 &quot;동기화 블록 인덱스(sync block index)&quot; 를 걸 수 있도록 객체 헤더를 설계해 두었습니다. 한 번도 락 대상으로 쓰이지 않는 객체조차 이 메커니즘을 위해 비용을 지불합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;잘못된 락 대상으로 인한 데드락 위험&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(this)&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(typeof(MyClass))&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(&quot;some-string&quot;)&lt;/code&gt; 처럼 외부에서 동일 참조에 접근 가능한 객체를 잠그면, 의도치 않은 코드와 락 경합&amp;middot;데드락이 발생합니다. 컴파일러는 이를 막을 수단이 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장의 한계&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt; 는 객체 헤더 비트를 사용하기 때문에 잠금 자체에 메타데이터를 더 붙이거나 진단 정보를 풍부하게 만들기 어렵습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 13 / .NET 9 의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt; 은 이 세 가지를 한꺼번에 정리합니다 &amp;mdash; &lt;b&gt;잠금만을 위한 전용 타입을 만들고, 컴파일러가 그 타입을 인식해 전용 API 로 변환&lt;/b&gt;하는 방식입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;2. 30초 요약 &amp;mdash; 한 줄만 바꾸면 됩니다&lt;/h2&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Before (.NET 8 이하 / 고전 패턴)
private readonly object _sync = new();

public void Increment()
{
    lock (_sync)            // &amp;rarr; Monitor.Enter / Monitor.Exit 로 변환
    {
        _count++;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// After (.NET 9 / C# 13)
private readonly System.Threading.Lock _sync = new();

public void Increment()
{
    lock (_sync)            // &amp;rarr; _sync.EnterScope() + Scope.Dispose() 로 변환
    {
        _count++;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;바뀐 것은 필드 타입 한 줄뿐입니다.&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드는 그대로 유지하지만, 컴파일러가 락 대상의 정적 타입을 보고 완전히 다른 IL 을 생성합니다. 호출부 코드를 한 글자도 건드리지 않고도 새 API 의 이점을 받습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;3. ADEPT &amp;mdash; 비유로 이해하는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; vs &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(object)&lt;/code&gt;&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;A (Analogy) &amp;mdash; 회의실 사용&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(object)&lt;/code&gt; (고전)&lt;/b&gt;: 사무실에 회의실이 따로 없습니다. 회의가 필요하면 &lt;b&gt;임의의 빈 책상 한 자리&lt;/b&gt;를 골라 &quot;지금부터 여기 회의 중&quot; 이라고 표시판을 둡니다. 표시판은 이 책상에 앉던 사람의 책상 헤더(객체 헤더의 sync block index) 에 붙입니다. 모든 책상이 표시판을 받을 수 있도록 처음부터 그 슬롯이 마련되어 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; (.NET 9)&lt;/b&gt;: 회의실 전용 공간을 만듭니다. 그 공간을 잡는 도구도 회의실에 내장되어 있습니다(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;EnterScope()&lt;/code&gt;). 일반 책상은 더 이상 회의용 표시판 슬롯을 비워둘 필요가 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;D (Diagram) &amp;mdash; 컴파일러가 두 갈래로 갈라지는 지점&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/cWaF4d/dJMcadPqxWz/AAAAAAAAAAAAAAAAAAAAAN3f9B1cV_1vajCujkPPbOeh96Lq00dPPs_GACPz183V/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=UP6Qb7MxgZgeVcE7PO1%2F%2B5QcnXI%3D&quot; alt=&quot;컴파일러는 락 대상의 정적 타입을 본다&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;E (Example) &amp;mdash; 실제 동작&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Threading;

public sealed class Counter
{
    private readonly Lock _sync = new();
    private int _count;

    public void Increment()
    {
        lock (_sync)
        {
            _count++;
        }
    }

    public int Read()
    {
        lock (_sync)
        {
            return _count;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;호출부에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드를 그대로 사용했지만 &amp;mdash; &lt;b&gt;컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_sync&lt;/code&gt; 의 타입이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt; 인 것을 보고&lt;/b&gt; 자동으로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;EnterScope()&lt;/code&gt; 호출 + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope.Dispose()&lt;/code&gt; 패턴으로 IL 을 생성합니다. 다음 4번 절에서 IL 로 직접 확인합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;P (Plain language) &amp;mdash; 한 문장&lt;/h3&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;락 대상의 타입만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 으로 바꾸면, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드는 자동으로 새 API 로 변환됩니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;T (Test your understanding)&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private object _sync = new Lock();&lt;/code&gt; &amp;mdash; 이렇게 선언하면 새 API 가 호출될까요? *(힌트: 정적 타입은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 입니다.)*&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock((object)_sync) { ... }&lt;/code&gt; 처럼 명시적으로 캐스팅하면 어떻게 될까요?&lt;/li&gt;
&lt;li&gt;둘 다 컴파일은 되지만, &lt;b&gt;새 API 가 호출되지 않고 컴파일러 경고 CS9216 이 뜹니다.&lt;/b&gt; 6번 절에서 자세히 다룹니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;4. IL 로 직접 확인하기 &amp;mdash; 두 코드는 정말 다른 IL 로 컴파일됩니다&lt;/h2&gt;
&lt;blockquote style=&quot;background: #ebfbee; border-left: 4px solid #40c057; border-radius: 0 8px 8px 0; padding: 14px 18px; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;이 글의 핵심 주장은 &quot;컴파일러가 락 대상의 타입을 보고 다른 IL 을 만든다&quot; 입니다. 추측이 아니라 IL 디컴파일 결과로 직접 확인합니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.1 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 락 &amp;mdash; 고전 경로&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public sealed class CounterObject
{
    private readonly object _sync = new();
    private int _count;

    public void Increment()
    {
        lock (_sync) { _count++; }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Increment()&lt;/code&gt; 의 IL 핵심부 (Release, .NET 9):&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;.method public hidebysig instance void Increment() cil managed
{
    .locals init (object V_0, bool V_1)
    ldarg.0
    ldfld      object CounterObject::_sync
    stloc.0
    ldc.i4.0
    stloc.1
    .try
    {
        ldloc.0
        ldloca.s   V_1
        call       void [System.Threading]System.Threading.Monitor::Enter(object, bool&amp;amp;)
        // _count++
        ldarg.0
        dup
        ldfld      int32 CounterObject::_count
        ldc.i4.1
        add
        stfld      int32 CounterObject::_count
        leave.s    EndFinally
    }
    finally
    {
        ldloc.1
        brfalse.s  EndOfFinally
        ldloc.0
        call       void [System.Threading]System.Threading.Monitor::Exit(object)
        EndOfFinally: endfinally
    }
    EndFinally: ret
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter(object, bool&amp;amp;)&lt;/code&gt; 와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit(object)&lt;/code&gt; 호출이 명시적으로 박혀 있습니다. 락 대상은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 로 다뤄지고, 객체 헤더의 sync block index 가 사용됩니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.2 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 락 &amp;mdash; 새 경로&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public sealed class CounterLock
{
    private readonly System.Threading.Lock _sync = new();
    private int _count;

    public void Increment()
    {
        lock (_sync) { _count++; }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Increment()&lt;/code&gt; 의 IL 핵심부:&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;.method public hidebysig instance void Increment() cil managed
{
    .locals init (valuetype [System.Threading]System.Threading.Lock/Scope V_0)
    ldarg.0
    ldfld      class [System.Threading]System.Threading.Lock CounterLock::_sync
    callvirt   instance valuetype [System.Threading]System.Threading.Lock/Scope
               [System.Threading]System.Threading.Lock::EnterScope()
    stloc.0
    .try
    {
        ldarg.0
        dup
        ldfld      int32 CounterLock::_count
        ldc.i4.1
        add
        stfld      int32 CounterLock::_count
        leave.s    EndFinally
    }
    finally
    {
        ldloca.s   V_0
        call       instance void
                   [System.Threading]System.Threading.Lock/Scope::Dispose()
        endfinally
    }
    EndFinally: ret
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt; 가 사라졌습니다. 대신 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock::EnterScope()&lt;/code&gt; 가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;valuetype Lock/Scope&lt;/code&gt; 를 반환하고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally&lt;/code&gt; 절에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope::Dispose()&lt;/code&gt; 가 호출됩니다. 로컬 변수 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;V_0&lt;/code&gt; 의 타입이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;valuetype Lock/Scope&lt;/code&gt; 인 것에 주목하세요 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;class&lt;/code&gt; 가 아니라 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;valuetype&lt;/code&gt; 입니다. 이것이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct&lt;/code&gt; 의 흔적입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.3 핵심 차이를 표로&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(object)&lt;/code&gt;&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(Lock)&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;호출하는 API&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock.EnterScope()&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope.Dispose()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 식별자&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;객체 헤더의 sync block index&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 인스턴스의 전용 필드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;진입 경로 비용&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;헤더 비트 검사 + (경합 시) 동기화 블록 인덱스 할당&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;전용 필드 CAS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/finally&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;C# 컴파일러가 직접 생성&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;컴파일러가 직접 생성 (Scope.Dispose 호출)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;로컬 변수&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;bool lockTaken&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock.Scope&lt;/code&gt; (ref struct)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;객체 헤더 사용&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;사용함&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;사용 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;5. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock.EnterScope()&lt;/code&gt; 와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct Scope&lt;/code&gt; &amp;mdash; 왜 굳이 ref struct 인가&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;EnterScope()&lt;/code&gt; 의 시그니처는 다음과 같습니다 (개념적 표현).&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;namespace System.Threading;

public sealed class Lock
{
    public Scope EnterScope();          // ⬅ ref struct 반환
    public bool TryEnter();
    public bool TryEnter(int millisecondsTimeout);
    public bool TryEnter(TimeSpan timeout);
    public void Enter();
    public void Exit();
    public bool IsHeldByCurrentThread { get; }

    public ref struct Scope             // ⬅ ref struct
    {
        public void Dispose();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope&lt;/code&gt; 가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct&lt;/code&gt; 인 것은 &lt;b&gt;단순한 스타일 선택이 아니라 의미적 보장&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.1 ref struct 가 강제하는 제약&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct&lt;/code&gt; 는 다음을 컴파일 시점에 보장합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스택에만 존재할 수 있다&lt;/b&gt; &amp;mdash; 힙 할당 없음, 박싱 불가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;필드로 저장 불가&lt;/b&gt; &amp;mdash; 클래스의 필드, 다른 (일반) 구조체의 필드가 될 수 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드에서 await 경계를 넘을 수 없음&lt;/b&gt; &amp;mdash; 상태 머신이 힙으로 캡처할 수 없으므로&lt;/li&gt;
&lt;li&gt;&lt;b&gt;iterator (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;yield return&lt;/code&gt;) 안에서 yield 경계를 넘을 수 없음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;람다에 캡처될 수 없음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다른 스레드로 전달될 수 없음&lt;/b&gt; &amp;mdash; 값이지만 ref-like 라서 의도적으로 한 스레드의 스택에 묶임&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.2 왜 락 해제에 이 제약이 필요한가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;락 해제(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt; 든 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock.Exit&lt;/code&gt; 든)는 &lt;b&gt;락을 획득한 그 스레드에서만&lt;/b&gt; 호출되어야 합니다. 다른 스레드에서 호출하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationLockException&lt;/code&gt; 이 터지거나 정의되지 않은 동작입니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope&lt;/code&gt; 가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct&lt;/code&gt; 라는 것은 컴파일러가 다음을 막는다는 뜻입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✗ 컴파일 에러: ref struct 는 필드로 저장 불가
class Holder
{
    Lock.Scope _s;
}

// ✗ 컴파일 에러: ref struct 는 await 경계를 넘을 수 없음
async Task Bad()
{
    using (var s = _lock.EnterScope())
    {
        await Task.Delay(100);   // CS4007 / CS8350 류 에러
        // 만약 허용되면 await 후 다른 스레드에서 Dispose 가 호출될 수 있음
    }
}

// ✗ 람다 캡처 불가
Action a = () =&amp;gt;
{
    using (var s = _lock.EnterScope())   // s 는 캡처 가능한 일반 변수지만
    {
        // 람다 외부로 새어 나갈 수 없음 (ref struct 제약)
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct&lt;/code&gt; 는 &quot;락을 잡은 스레드의 스택 프레임을 떠나기 전에 반드시 해제하라&quot; 는 규칙을 컴파일 시점에 강제합니다.&lt;/b&gt; 고전 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(object)&lt;/code&gt; 에서는 컴파일러가 try/finally 를 만들어주지만, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lockTaken&lt;/code&gt; 변수를 그냥 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;bool&lt;/code&gt; 로 보관하므로 약속의 강도가 다릅니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.3 성능 측면 &amp;mdash; 핫 패스 최적화&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct&lt;/code&gt; 라는 사실 자체와 별개로, 새 API 는 다음 최적화 여지를 얻습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Dispose 가 같은 스레드에서 불린다는 보장&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;EnterScope&lt;/code&gt; 가 락을 획득한 스레드와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope.Dispose()&lt;/code&gt; 가 호출되는 스레드가 동일하다는 것을 컴파일러가 보장하므로, 해제 경로에서 &quot;현재 스레드 ID&quot; 를 다시 조회하지 않아도 됩니다 (스레드-static 룩업 회피).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;객체 헤더의 sync block 활용 안 함&lt;/b&gt; &amp;mdash; 락 식별자가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 인스턴스의 전용 필드이므로, 일반 객체에 강제로 부여되던 비용을 우회합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JIT 인라이닝 여지&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;EnterScope&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope.Dispose&lt;/code&gt; 는 작은 메서드라 인라이닝되기 쉽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;대략적인 지표로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt; 대비 &lt;b&gt;무경합 락(uncontended lock) 진입/해제 비용이 약 25% 줄어든다는 벤치마크 보고&lt;/b&gt;가 여러 출처에서 인용됩니다 ([InfoWorld](https://www.infoworld.com/article/3632180/how-to-use-the-new-lock-object-in-c-sharp-13.html), [Anthony Giretti](https://anthonygiretti.com/2025/03/05/c-13-introducing-system-threading-lock/)). 다만 &lt;b&gt;경합이 심한 시나리오에서는 락 자체의 대기 시간이 지배적&lt;/b&gt;이므로, 무경합 비용 절감의 체감 효과는 워크로드에 따라 다릅니다. &lt;b&gt;실제 측정해서 확인&lt;/b&gt; 하는 자세를 권장합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;6. 함정 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 캐스팅&amp;middot;할당 시 새 API 가 사라진다&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 가장 자주 미끄러지는 지점입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.1 컴파일러는 정적 타입만 본다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 문이 새 API 로 변환되는 조건은 &lt;b&gt;락 대상 표현식의 정적 타입이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt; 인지&lt;/b&gt; 입니다. 런타임에 어떤 객체인지가 아니라 컴파일러가 보는 타입입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;private readonly Lock _sync = new();

void Case1()
{
    lock (_sync) { /* OK &amp;mdash; 새 API */ }
}

void Case2()
{
    object boxed = _sync;
    lock (boxed) { /* ✗ 새 API 아님 &amp;mdash; boxed 의 정적 타입이 object */ }
}

void Case3()
{
    lock ((object)_sync) { /* ✗ 새 API 아님 &amp;mdash; 캐스트로 정적 타입이 object */ }
}

void Case4(object o)
{
    lock (o) { /* ✗ 새 API 아님 &amp;mdash; 매개변수 타입이 object */ }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.2 CS9216 &amp;mdash; 컴파일러가 미리 알려준다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 13 컴파일러는 위와 같은 코드에 &lt;b&gt;CS9216 경고&lt;/b&gt;를 발생시킵니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #fff5f5; border-left: 4px solid #fa5252; border-radius: 0 8px 8px 0; padding: 14px 18px; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;warning CS9216&lt;/b&gt;: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement.&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 경고가 떴다는 것은 &quot;당신은 새 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 타입을 선언했지만, 이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 문에서는 옛 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt; 경로로 폴백되었다&quot; 는 신호입니다. &lt;b&gt;경고 무시 금지&lt;/b&gt; &amp;mdash; 의도된 폴백이라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 이 아니라 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 를 락 객체로 선언해야 의도가 명확해집니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.3 대표적인 함정 시나리오&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컬렉션에 락 객체 보관&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dictionary&amp;lt;string, object&amp;gt;&lt;/code&gt; 같은 자료구조에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 을 담아 꺼내면 정적 타입이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 가 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;제네릭 컨테이너의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;T = object&lt;/code&gt;&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;List&amp;lt;object&amp;gt;&lt;/code&gt; 에 담는 경우.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인터페이스로 받는 헬퍼 메서드&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void RunUnderLock(object lockObj, Action body)&lt;/code&gt; 처럼 디자인된 헬퍼에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 을 넘기면 헬퍼 내부에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리플렉션&amp;middot;동적 타입&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;dynamic&lt;/code&gt; 변수에 담거나 리플렉션으로 꺼내는 경로.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 을 도입할 때는 &lt;b&gt;락 객체의 선언과 사용 모두에서 정적 타입을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 으로 유지&lt;/b&gt;하는 것이 원칙입니다. 헬퍼 메서드를 둔다면 인자 타입을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 으로 받도록 다시 설계합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✗ 폴백 함정
static void RunUnderLock(object lockObj, Action body)
{
    lock (lockObj) { body(); }   // lockObj 정적 타입은 object
}

// ✓ Lock 인지 그대로 유지
static void RunUnderLock(Lock lockObj, Action body)
{
    lock (lockObj) { body(); }   // 새 API 로 변환됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;7. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 패턴으로 명시적으로 쓰기&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드 대신 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;EnterScope()&lt;/code&gt; 를 직접 부를 수도 있습니다. 동작은 동일하지만, &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 블록보다 더 풍부한 범위 제어&lt;/b&gt;가 필요할 때 쓸 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public int ReadCount()
{
    using Lock.Scope scope = _sync.EnterScope();
    return _count;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;또는 명시적 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 블록.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;pgsql&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public void Update(Func&amp;lt;int, int&amp;gt; update)
{
    using (_sync.EnterScope())
    {
        _count = update(_count);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;언제 굳이 명시적 형태를 쓰는가?&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;TryEnter()&lt;/code&gt; 와 함께 조건부 진입을 다룰 때 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드는 항상 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enter&lt;/code&gt; 하므로.&lt;/li&gt;
&lt;li&gt;읽는 사람에게 &quot;이건 단순 락이 아니라 Scope 가 만들어진다&quot; 는 점을 명시하고 싶을 때.&lt;/li&gt;
&lt;li&gt;락 객체와 Scope 의 라이프타임을 코드 리뷰에서 더 명확히 드러내고 싶을 때.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;대부분의 일반적인 동기화에서는 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드를 그대로 쓰는 편이 가독성이 좋습니다.&lt;/b&gt; 컴파일러가 이미 같은 IL 을 만들어주니, 의미 차이가 있을 때만 명시적 형태를 선택합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 8 ===================== --&gt;
&lt;h2 id=&quot;section-8&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;8. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;TryEnter&lt;/code&gt; &amp;mdash; 시간 제한이 있는 락 시도&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.TryEnter(object, TimeSpan)&lt;/code&gt; 의 새 API 대응은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock.TryEnter(TimeSpan)&lt;/code&gt; 입니다. 다만 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드와 결합되는 형태는 없으므로&lt;/b&gt;, 직접 호출해야 합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;private readonly Lock _sync = new();

public bool TryUpdate(Action body, TimeSpan timeout)
{
    if (!_sync.TryEnter(timeout))
        return false;

    try
    {
        body();
        return true;
    }
    finally
    {
        _sync.Exit();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;TryEnter&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enter&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt; 직접 호출 경로는 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope&lt;/code&gt; 의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct&lt;/code&gt; 보호를 받지 않습니다.&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/finally&lt;/code&gt; 와 호출 짝맞추기는 직접 책임집니다. 가능하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;EnterScope()&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 을 우선 고려하고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;TryEnter&lt;/code&gt; 가 정말로 필요한 경우에만 직접 사용합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 9 ===================== --&gt;
&lt;h2 id=&quot;section-9&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;9. 마이그레이션 가이드 &amp;mdash; 점진적으로 옮기기&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;9.1 한 번에 한 클래스씩&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;큰 코드베이스에서 모든 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 락을 한 번에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 으로 바꾸는 것은 위험합니다. 다음 순서를 권장합니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;공유 자원이 있는 클래스를 식별&lt;/b&gt; &amp;mdash; 락이 같은 인스턴스 안에서만 쓰이는 경우가 안전합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;필드 타입 변경&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly object _sync = new();&lt;/code&gt; &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly Lock _sync = new();&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빌드 + 경고 확인&lt;/b&gt; &amp;mdash; CS9216 이 뜨는 위치를 모두 점검합니다. 새 API 가 적용되지 않는 호출 경로를 발견하는 신호입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;헬퍼&amp;middot;인자 시그니처 정리&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;RunUnderLock(object, Action)&lt;/code&gt; 류 헬퍼는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 으로 다시 선언합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단위 테스트 / 통합 테스트&lt;/b&gt; &amp;mdash; 동기화 로직 자체는 동일하지만, 회귀 가능성을 점검합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;9.2 절대 같이 쓰지 말 것&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 인스턴스를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 락으로도 쓰는 혼용&lt;/b&gt; &amp;mdash; 예: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock _sync&lt;/code&gt; 이지만 일부 코드에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock((object)_sync)&lt;/code&gt; 로 사용. 이 경우 두 메커니즘이 동시에 한 객체에 걸려 동작이 미정의입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Hashtable&lt;/code&gt; 키나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dictionary&lt;/code&gt; 키로 사용&lt;/b&gt; &amp;mdash; 정적 타입이 흐려지면 새 API 가 사라집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;9.3 무엇이 바뀌지 않는가&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;재진입(reentrancy) 의미&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt; 와 마찬가지로 같은 스레드의 재진입을 허용합니다 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;recursive&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공정성(fairness)&lt;/b&gt; &amp;mdash; 두 메커니즘 모두 강한 공정성 보장은 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데드락 가능성&lt;/b&gt; &amp;mdash; 락 객체가 바뀌었을 뿐 락의 의미는 같습니다. 락 순서 규칙은 그대로 지켜야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 10 ===================== --&gt;
&lt;h2 id=&quot;section-10&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;10. Unity 환경에서의 주의&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;10.1 .NET / C# 버전 호환성&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt; 은 &lt;b&gt;.NET 9 BCL 에 포함된 타입&lt;/b&gt;이고, 컴파일러 변환 동작은 &lt;b&gt;C# 13&lt;/b&gt; 부터입니다. Unity 에서 사용 가능한지는 다음 두 축으로 확인합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Unity 의 C# 컴파일러 버전&lt;/b&gt; &amp;mdash; Unity 6 이상이 C# 13 / .NET 9 까지 지원하는 방향으로 가고 있지만, &lt;b&gt;사용 중인 Unity 정확한 버전의 릴리스 노트와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Edit &amp;gt; Project Settings &amp;gt; Player &amp;gt; Other Settings &amp;gt; Api Compatibility Level&lt;/code&gt; 설정을 직접 확인&lt;/b&gt;하세요. 검증되지 않은 버전에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 을 쓰면 컴파일은 되더라도 IL2CPP 경로에서 런타임 동작이 다를 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt; 타입 가용성&lt;/b&gt; &amp;mdash; Unity 의 BCL 어셈블리(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;mscorlib&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading&lt;/code&gt;)에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 클래스가 포함되어야 합니다. 미지원 Unity 버전에서는 &lt;b&gt;타입을 못 찾아 컴파일 에러&lt;/b&gt;가 납니다. 사용 전에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 심볼이 IDE 에서 즉시 인식되는지 먼저 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;background: #fff5f5; border-left: 4px solid #fa5252; border-radius: 0 8px 8px 0; padding: 14px 18px; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;주의: Unity 의 Mono / IL2CPP 백엔드에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 의 ref struct 동작 (스택 강제, async 경계 막기 등)이 동일하게 강제되는지는 사용 중인 정확한 Unity 버전에 따라 다릅니다. &lt;b&gt;추정으로 판단하지 말고, 타겟 Unity 버전에서 테스트로 확인&lt;/b&gt;하세요.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;10.2 게임 핫 패스에서의 가이드&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메인 스레드 단독 코드에는 락을 걸지 않는다&lt;/b&gt; &amp;mdash; Unity 의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MonoBehaviour&lt;/code&gt; API 대부분은 메인 스레드 한정입니다. 락 자체가 불필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백그라운드 스레드 &amp;harr; 메인 스레드 데이터 교환에만 사용&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Job System&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;, custom thread 에서 메인 스레드와 공유하는 자료구조에만 동기화를 적용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;무경합 락이 잦다면 새 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 타입의 이점이 큽니다&lt;/b&gt; &amp;mdash; 게임 루프에서 매 프레임 한 번 잡고 푸는 패턴이라면, sync block index 를 우회하는 새 API 가 GC 압박과 진입 비용 둘 다 줄여줍니다 (이론상). 측정으로 확인하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 안에서 락 잡지 말 것&lt;/b&gt; &amp;mdash; 새 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope&lt;/code&gt; 는 ref struct 라 await 경계를 넘을 수 없습니다. 컴파일 에러로 실수가 차단됩니다 (이 자체가 안전 보장의 일부입니다).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 11 ===================== --&gt;
&lt;h2 id=&quot;section-11&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;11. 자주 묻는 질문&lt;/h2&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;Q1. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Wait&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Pulse&lt;/code&gt; 도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 에서 쓸 수 있나요?&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;A. &lt;b&gt;쓸 수 없습니다.&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Wait/Pulse/PulseAll&lt;/code&gt; 은 객체 헤더의 sync block 을 사용하는데, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 은 그 메커니즘을 우회합니다. 조건 변수 패턴이 필요하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ManualResetEventSlim&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt; 등 다른 동기화 프리미티브를 사용하거나 기존 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(object)&lt;/code&gt; 를 유지합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;Q2. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 인스턴스 자체는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;static&lt;/code&gt; 으로 선언해도 되나요?&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;A. 됩니다. 다만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;static&lt;/code&gt; 락은 모든 인스턴스가 공유하므로 경합 가능성이 커집니다. 락의 보호 범위(인스턴스 단위 vs 타입 단위)를 의식해서 선택합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;Q3. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 도 직렬화 대상이 될 수 있나요?&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;A. &lt;b&gt;하지 마세요.&lt;/b&gt; 락 객체는 직렬화&amp;middot;역직렬화 의미가 없습니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;[NonSerialized]&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;[JsonIgnore]&lt;/code&gt; 등으로 제외합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;Q4. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(this)&lt;/code&gt; 대신 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock _this = new()&lt;/code&gt; 를 두면 되나요?&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;A. 의도는 맞지만, 더 좋은 답은 &quot;락 객체를 외부에 노출하지 말 것&quot; 입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly Lock _sync = new();&lt;/code&gt; 로 클래스 내부에 가둡니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;Q5. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 과 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim(1, 1)&lt;/code&gt; 중 무엇을 쓰나요?&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;A. &lt;b&gt;동기 메서드 안에서 짧은 임계 영역&lt;/b&gt;이면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt;. &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 와 함께 임계 영역을 보호&lt;/b&gt;해야 하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt; 입니다 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAsync&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Release&lt;/code&gt;). 두 도구의 용도는 겹치지 않습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 12 ===================== --&gt;
&lt;h2 id=&quot;section-12&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;12. 정리&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;C# 13 / .NET 9 의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt;&lt;/b&gt; 은 락 전용 타입입니다. 객체 헤더의 sync block 메커니즘을 우회하고, 진입&amp;middot;해제 경로를 짧게 만듭니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드 사용법은 동일&lt;/b&gt;하지만, 컴파일러는 락 대상의 정적 타입이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock&lt;/code&gt; 인지 보고 다른 IL 을 생성합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter/Exit&lt;/code&gt; 가 사라지고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;EnterScope()&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope.Dispose()&lt;/code&gt; 로 바뀝니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Scope&lt;/code&gt; 가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ref struct&lt;/code&gt;&lt;/b&gt; 인 것은 &quot;락을 획득한 스레드의 스택을 떠나기 전에 반드시 해제하라&quot; 는 규칙을 컴파일 시점에 강제하기 위해서입니다. async 경계, 람다 캡처, 필드 저장, 다른 스레드 전달이 모두 차단됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 로 캐스팅&amp;middot;할당하는 순간 새 API 는 사라집니다&lt;/b&gt; &amp;mdash; CS9216 경고를 보면 의도치 않은 폴백입니다. 컬렉션&amp;middot;헬퍼 인자&amp;middot;dynamic 등의 경로를 점검합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;마이그레이션은 한 줄 변경&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly object _sync = new();&lt;/code&gt; &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly Lock _sync = new();&lt;/code&gt; 호출부는 그대로 유지됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Unity 에서는 타겟 버전의 C# / .NET 호환성을 먼저 확인&lt;/b&gt;한 뒤 사용합니다. 메인 스레드 단독 코드에는 락 자체가 불필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;다음 글([14. 경쟁 상태와 데드락](../14.%20경쟁%20상태와%20데드락))에서는 락이 잘못 쓰일 때 발생하는 경쟁 상태(race condition)와 데드락(deadlock)을 다룹니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 13 ===================== --&gt;
&lt;h2 id=&quot;section-13&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[Lock Class (System.Threading) &amp;mdash; Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/api/system.threading.lock?view=net-9.0)&lt;/li&gt;
&lt;li&gt;[Obey lock object semantics for lock statements &amp;mdash; C# 13.0 feature spec](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-13.0/lock-object)&lt;/li&gt;
&lt;li&gt;[The lock statement &amp;mdash; C# reference](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/lock)&lt;/li&gt;
&lt;li&gt;[Add first class System.Threading.Lock type &amp;mdash; dotnet/runtime#34812](https://github.com/dotnet/runtime/issues/34812)&lt;/li&gt;
&lt;li&gt;[How to use the new Lock object in C# 13 &amp;mdash; InfoWorld](https://www.infoworld.com/article/3632180/how-to-use-the-new-lock-object-in-c-sharp-13.html)&lt;/li&gt;
&lt;li&gt;[C# 13: Introducing System.Threading.Lock &amp;mdash; Anthony Giretti](https://anthonygiretti.com/2025/03/05/c-13-introducing-system-threading-lock/)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>C#</category>
      <category>기초</category>
      <category>입문</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/583</guid>
      <comments>https://everyday-devup.tistory.com/583#entry583comment</comments>
      <pubDate>Sat, 9 May 2026 00:52:25 +0900</pubDate>
    </item>
    <item>
      <title>[PART13.비동기와 스레딩 기초(12/15)] lock 문 &amp;mdash; 임계 구역을 한 스레드만 통과시키기</title>
      <link>https://everyday-devup.tistory.com/582</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(12/15)] lock 문 &amp;mdash; 임계 구역을 한 스레드만 통과시키기&lt;/h1&gt;
&lt;p style=&quot;color: #868e96; font-size: 14px; margin: 0 0 32px; line-height: 1.6;&quot; data-ke-size=&quot;size16&quot;&gt;한 번에 한 스레드만 들여보내는 단일 차로 / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt;가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/finally&lt;/code&gt;로 감싸진 것의 문법 설탕 / 락 객체는 반드시 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly object&lt;/code&gt;&lt;/p&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;[문제 제기] `_counter++` 한 줄이 어떻게 값을 잃어버리는가&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;[개념 정의] 임계 구역 &amp;middot; 상호 배제 &amp;middot; Monitor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;[작동 원리] `lock`은 `try/finally`로 감싼 `Monitor.Enter`/`Exit`다 &amp;mdash; IL로 직접 본다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;[락 객체 선택의 규칙] 왜 `private readonly object`인가&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;[락 안에서 await 금지] 스레드 소유권이라는 모델&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;[데드락] 두 락을 다른 순서로 잡으면 영원히 풀리지 않는다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;[성능 특성] 무경합은 매우 싸고, 경합은 점점 비싸진다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-8&quot;&gt;[Unity 실전 패턴] 메인 스레드 모델과 lock의 사용처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-9&quot;&gt;[다른 동기화 도구와의 비교] lock이 답이 아닌 경우&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-10&quot;&gt;[자주 하는 실수와 해법]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-11&quot;&gt;[버전별 변화]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-12&quot;&gt;[요약]&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[문제 제기] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_counter++&lt;/code&gt; 한 줄이 어떻게 값을 잃어버리는가&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 모바일 게임에서 백그라운드 다운로더가 동시에 8개의 에셋 번들을 받아옵니다. 각 다운로드가 완료될 때마다 진행률 카운터를 1씩 올려서 UI에 표시한다고 해봅시다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class DownloadProgress
{
    private int _completed;

    public void OnAssetCompleted()
    {
        _completed++; // 한 줄이지만 안전하지 않습니다.
    }

    public int GetCompleted() =&amp;gt; _completed;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드를 8개의 스레드가 동시에 호출하면, 종료 시점에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_completed&lt;/code&gt;는 8이 아닐 수 있습니다. 6이나 7이 나올 때도 있고, 운이 나쁘면 4가 될 때도 있습니다. &lt;b&gt;다운로드는 분명 8개가 끝났는데 카운터가 일치하지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;원인은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_completed++&lt;/code&gt; 한 줄이 사실은 &lt;b&gt;세 단계&lt;/b&gt;로 동작하기 때문입니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메모리에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_completed&lt;/code&gt; 값을 CPU 레지스터로 읽기 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ldfld&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;레지스터에서 1을 더하기 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;add&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;레지스터의 값을 다시 메모리에 쓰기 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;stfld&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;스레드 A가 1단계를 마친 직후 OS가 스레드를 바꿔치기 합니다. 스레드 B가 1&amp;middot;2&amp;middot;3단계를 모두 끝내고 나서 스레드 A가 다시 깨어나 자기가 읽었던 옛날 값에 1을 더해 메모리에 씁니다. &lt;b&gt;스레드 B의 작업이 통째로 사라집니다.&lt;/b&gt; 이것이 경쟁 상태(Race Condition)입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/cPvQRk/dJMcajhPzk0/AAAAAAAAAAAAAAAAAAAAAOktKfbCML_7c2v_m_uq6d6WWkQPD6-sMoKGa4Q5BzZe/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=irWFi%2FUQa1MjBHge8r5CCLL42sw%3D&quot; alt=&quot;_counter++ 의 경쟁 상태 시나리오 (시작값 5)&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 막는 가장 직관적인 도구가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 문입니다. 이 글에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;이 IL 레벨에서 어떤 코드로 변환되는지, 왜 락 객체로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;this&lt;/code&gt;나 문자열을 쓰면 안 되는지, 왜 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;이 금지되는지를 파헤쳐봅니다. 그리고 Unity 메인 스레드 모델에서 어떤 패턴으로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;을 활용해야 하는지까지 봅니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[개념 정의] 임계 구역 &amp;middot; 상호 배제 &amp;middot; Monitor&lt;/h2&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;임계 구역(Critical Section)&lt;/b&gt; 여러 스레드가 동시에 실행하면 안 되는 코드 블록. 공유 자원(필드&amp;middot;컬렉션&amp;middot;파일 핸들 등)을 읽거나 쓰는 부분이 대부분 여기에 해당한다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_counter++&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_queue.Enqueue(item)&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_dict[key] = value&lt;/code&gt; 같이 공유 상태를 만지는 짧은 구간.&lt;/blockquote&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;상호 배제(Mutual Exclusion)&lt;/b&gt; 한 시점에 단 하나의 스레드만 임계 구역에 들어가도록 강제하는 규칙. 줄여서 mutex라고 부른다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; 화장실 한 칸에 한 명만 들어갈 수 있는 것과 같은 모델.&lt;/blockquote&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt; 클래스&lt;/b&gt; .NET 런타임이 제공하는 동기화 프리미티브. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter(obj)&lt;/code&gt;로 들어가고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit(obj)&lt;/code&gt;로 나온다. 이미 누가 점유한 락이면 호출 스레드는 차단된다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(obj) { ... }&lt;/code&gt;는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt;를 자동으로 감싸주는 문법 설탕이다.&lt;/blockquote&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 문 (lock statement)&lt;/b&gt; C# 언어 키워드. 참조 타입 객체 하나를 받아 그 객체에 대한 모니터를 잡고 해당 블록을 임계 구역으로 만든다. 컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt;와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/finally&lt;/code&gt;로 풀어 준다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock (_sync) { _counter++; }&lt;/code&gt;&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;은 새로운 동기화 메커니즘이 아닙니다. 이미 .NET에 있던 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt; 클래스를 더 안전하고 짧게 쓰기 위한 &lt;b&gt;언어 차원의 단축키&lt;/b&gt;입니다. 그래서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;을 이해하려면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt;가 어떤 일을 하는지부터 봐야 합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/pJb9r/dJMcajhPzk1/AAAAAAAAAAAAAAAAAAAAALiuHxBqUchEEuM-Be1NjMrhXLYa_HWeRpw-TTVDUnUz/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=EeRi0Bk6bUxJidaveDdNt53NMJM%3D&quot; alt=&quot;lock &amp;mdash; 한 칸짜리 화장실 모델&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 두 가지입니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락의 정체는 &lt;b&gt;객체 하나&lt;/b&gt;입니다. CLR이 그 객체의 헤더에 어떤 스레드가 락을 점유 중인지 기록합니다.&lt;/li&gt;
&lt;li&gt;락은 &lt;b&gt;재진입(re-entrant)&lt;/b&gt; 가능합니다. 같은 스레드가 같은 락을 두 번 잡아도 데드락에 빠지지 않고 카운트만 1 늘어납니다. 다만 Exit도 그만큼 호출해야 풀립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[작동 원리] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/finally&lt;/code&gt;로 감싼 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt;다 &amp;mdash; IL로 직접 본다&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;가장 단순한 형태&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class Counter
{
    private readonly object _sync = new();
    private int _value;

    public void Increment()
    {
        lock (_sync)
        {
            _value++;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 컴파일러는 위 코드를 다음과 거의 동일한 코드로 풀어 씁니다 (C# 4 이후).&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public void Increment()
{
    object obj = _sync;
    bool lockTaken = false;
    try
    {
        Monitor.Enter(obj, ref lockTaken);
        _value++;
    }
    finally
    {
        if (lockTaken) Monitor.Exit(obj);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 두 가지가 핵심입니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;락 대상이 되는 참조를 지역 변수에 먼저 복사&lt;/b&gt;합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(_sync)&lt;/code&gt; 라고 썼지만 실제로는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;obj = _sync&lt;/code&gt;로 복사한 뒤 그 사본으로 Enter/Exit를 합니다. 임계 구역 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_sync&lt;/code&gt;가 다른 객체로 바뀌더라도 &lt;b&gt;들어갈 때 잡은 객체에 대해 Exit를 호출&lt;/b&gt;하기 위해서입니다. 이래서 락 객체는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;readonly&lt;/code&gt;여야 합니다 &amp;mdash; 어차피 컴파일러는 처음 잡은 객체 기준으로 닫으므로, 도중에 바꾸면 동기화가 깨집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;bool lockTaken&lt;/code&gt;이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enter&lt;/code&gt;에 ref로 전달&lt;/b&gt;됩니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt;가 락을 실제로 잡은 직후 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lockTaken = true&lt;/code&gt;로 셋팅됩니다. 이렇게 하는 이유는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enter&lt;/code&gt; 호출 자체가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThreadAbortException&lt;/code&gt; 같은 비동기 예외로 중단되더라도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lockTaken&lt;/code&gt;이 정확히 락 점유 여부를 반영하게 하기 위해서입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally&lt;/code&gt;는 락을 잡은 경우에만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt;를 호출합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;IL로 확인 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt;가 진짜 들어가는지&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;[/il-analysis] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Counter.Increment&lt;/code&gt; 메서드의 IL을 디컴파일하면 다음 골격이 나옵니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;pgsql&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;.method public hidebysig instance void Increment () cil managed
{
    .locals init (
        [0] object obj,
        [1] bool lockTaken)

    // obj = this._sync
    ldarg.0
    ldfld      object Counter::_sync
    stloc.0

    // lockTaken = false
    ldc.i4.0
    stloc.1
    .try
    {
        // Monitor.Enter(obj, ref lockTaken)
        ldloc.0
        ldloca.s   lockTaken
        call       void [System.Threading]System.Threading.Monitor::Enter(object, bool&amp;amp;)

        // _value++
        ldarg.0
        dup
        ldfld      int32 Counter::_value
        ldc.i4.1
        add
        stfld      int32 Counter::_value

        leave.s    END
    }
    finally
    {
        // if (lockTaken) Monitor.Exit(obj)
        ldloc.1
        brfalse.s  EXIT
        ldloc.0
        call       void [System.Threading]System.Threading.Monitor::Exit(object)
        EXIT: endfinally
    }
    END: ret
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;세 가지를 확인할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드의 IL 명령어는 따로 없습니다. &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;call Monitor::Enter&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;call Monitor::Exit&lt;/code&gt;&lt;/b&gt; 두 개로 구성된 일반 메서드 호출일 뿐입니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.try&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally&lt;/code&gt; 블록이 실제로 들어가서, 임계 구역에서 예외가 터져도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt;가 반드시 실행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enter(object, ref bool)&lt;/code&gt; 오버로드를 씁니다. 옛날(C# 3 이전) 컴파일러는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enter(object)&lt;/code&gt; 오버로드를 썼는데, 이 경우 &quot;Enter는 성공했는데 직후 예외가 나서 lockTaken을 false로 인식&quot;하는 미세한 누수가 있었습니다. ref 오버로드가 그 구멍을 막은 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Increment&lt;/code&gt; 메서드의 정확성 &amp;mdash; 락 안의 ++ 는 안전한가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(_sync)&lt;/code&gt; 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_value++&lt;/code&gt;는 안전합니다. 다른 스레드는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;이 풀릴 때까지 대기하고, 락 안에 있는 스레드는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;read &amp;rarr; +1 &amp;rarr; write&lt;/code&gt;를 끊김 없이 실행한 뒤 락을 풉니다. 다른 스레드가 끼어들 틈이 없습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;다만 카운터 한 개만 증가시키는 단순한 경우라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.Increment(ref _value)&lt;/code&gt;가 락 없이 더 빠릅니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;은 &lt;b&gt;여러 줄의 작업을 묶어서 일관성을 지켜야 할 때&lt;/b&gt; 진가를 발휘합니다 (예: 큐에서 dequeue + 처리 카운트 증가 + 마지막 처리 시각 기록).&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[락 객체 선택의 규칙] 왜 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly object&lt;/code&gt;인가&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;은 임의의 참조 타입 객체를 받습니다. 그래서 다음 코드는 모두 &lt;b&gt;컴파일은 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;sqf&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;lock (this)                  { /* ... */ }   // ❌ 위험
lock (typeof(MyClass))       { /* ... */ }   // ❌ 위험
lock (&quot;global_lock&quot;)         { /* ... */ }   // ❌❌ 매우 위험
lock (_publicField)          { /* ... */ }   // ❌ 위험
lock (_privateReadonlyObj)   { /* ... */ }   // ✓ 권장&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 13부터는 후자에 가깝게 강제하는 새 타입 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt;도 등장했지만(다음 글에서 다룹니다), &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 기반의 고전 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 문에서는 여전히 개발자가 규칙을 지켜야 합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/l34m8/dJMcaf7AdUC/AAAAAAAAAAAAAAAAAAAAAOyqjvEfyBMl81WRPyZNA52eWGESVlAFkbDpmH7RQlzK/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=aNRzOFtGy%2FY3oBG%2BOzYkkFeCQ9I%3D&quot; alt=&quot;왜 어떤 락 객체는 위험한가&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;1) &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(this)&lt;/code&gt; &amp;mdash; 외부에 노출된 자물쇠&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;this&lt;/code&gt;는 클래스 인스턴스 자신입니다. 그런데 그 인스턴스는 보통 &lt;b&gt;외부 코드가 변수로 들고 있는 객체&lt;/b&gt;입니다. 외부 코드가 우연히 또는 악의적으로 그 인스턴스에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;을 걸 수 있다는 뜻입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class Cache
{
    public void Refresh() { lock (this) { /* ... */ } } // 내부적으로 this 잠금
}

// 어딘가의 외부 코드
var cache = container.GetCache();
lock (cache) { Thread.Sleep(10000); } // 같은 객체를 외부가 10초 점유
// 이 사이 cache.Refresh()는 모두 블록됨 &amp;mdash; 락 탈취(lock hijacking)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이걸 &quot;내가 만든 자물쇠인데 다른 사람이 자기 마음대로 잠그고 안 풀어주는&quot; 상황이라고 비유할 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2) &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(typeof(X))&lt;/code&gt; &amp;mdash; 자물쇠가 전역으로 공유&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;typeof(X)&lt;/code&gt;는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Type&lt;/code&gt; 객체를 돌려주는데, 이 객체는 &lt;b&gt;AppDomain 안에서 클래스당 하나만 존재&lt;/b&gt;합니다. 즉 같은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;typeof(MyClass)&lt;/code&gt;는 어디서 부르든 같은 객체입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;class Foo
{
    public static void Op1() { lock (typeof(Foo)) { /* ... */ } } // 같은 Type을 잡음
}

class Bar  // 완전히 다른 클래스, 같은 라이브러리도 아님
{
    public static void Op2() { lock (typeof(Foo)) { /* ... */ } } // 같은 Type을 잡음
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Foo.Op1&lt;/code&gt;과 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Bar.Op2&lt;/code&gt;는 서로 무관해 보이지만 &lt;b&gt;사실상 같은 자물쇠를 두고 경쟁&lt;/b&gt;합니다. 어느 라이브러리가 어떤 Type을 잡고 있을지 예측할 수 없으므로 데드락 추적이 매우 어려워집니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3) &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(&quot;문자열 리터럴&quot;)&lt;/code&gt; &amp;mdash; 가장 위험한 안티패턴&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.NET&lt;/code&gt;은 메모리 효율을 위해 &lt;b&gt;문자열 인터닝(interning)&lt;/b&gt;을 합니다. 컴파일 타임에 등장한 동일한 리터럴은 런타임에서 &lt;b&gt;같은 단 하나의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;string&lt;/code&gt; 인스턴스&lt;/b&gt;로 합쳐집니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 두 메서드, 두 라이브러리에 흩어져 있음
void A() { lock (&quot;global_lock&quot;) { /* ... */ } }
void B() { lock (&quot;global_lock&quot;) { /* ... */ } }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;A&lt;/code&gt;의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;&quot;global_lock&quot;&lt;/code&gt;과 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;B&lt;/code&gt;의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;&quot;global_lock&quot;&lt;/code&gt;은 &lt;b&gt;메모리상 같은 객체&lt;/b&gt;입니다. NuGet으로 받은 라이브러리가 우연히 같은 문자열을 락으로 쓰면 우리 앱과 충돌합니다. 디버깅이 사실상 불가능합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4) &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly object _sync = new();&lt;/code&gt; &amp;mdash; 정답&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private&lt;/code&gt;&lt;/b&gt;: 외부가 같은 객체에 접근할 방법이 없으니 락 탈취가 원천 차단됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;readonly&lt;/code&gt;&lt;/b&gt;: 한 번 만들어진 락 객체가 다른 객체로 교체되지 않으므로, &quot;들어갈 때 잡은 객체&quot;와 &quot;나갈 때 푸는 객체&quot;가 항상 동일합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt;&lt;/b&gt;: 의미 없는 객체이므로 누구도 우연히 같은 객체를 잡을 일이 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;new()&lt;/code&gt;&lt;/b&gt;: 인스턴스마다 락도 별개입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Counter&lt;/code&gt; 두 개를 만들면 락도 두 개입니다 &amp;mdash; 한 인스턴스의 락이 다른 인스턴스를 막지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;정적(static) 필드를 잠그려면 락 객체도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;static&lt;/code&gt;으로 둡니다 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private static readonly object _staticSync = new();&lt;/code&gt;). 인스턴스 락으로는 클래스 전체의 정적 상태를 보호할 수 없습니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;값 타입은 락 객체로 쓸 수 없다&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;int x = 42;
lock (x) { /* ... */ } // 컴파일 에러 CS0185: 'lock' 문에 사용된 식은 참조 형식이어야 합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;만약 컴파일러가 박싱(boxing)을 해서 통과시킨다면 매번 새로운 박스 객체가 만들어져 동기화가 전혀 일어나지 않을 것입니다. 컴파일러는 그래서 아예 거부합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[락 안에서 await 금지] 스레드 소유권이라는 모델&lt;/h2&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task FetchAndStore()
{
    lock (_sync)
    {
        await SomeApiAsync(); // ❌ CS1996: 'lock' 문 본문에서는 await을 사용할 수 없습니다.
        _cache = result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 컴파일러는 이 코드를 컴파일하지 않습니다. 이유는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt;의 동작 모델에 있습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt;의 스레드 소유권(Thread Affinity)&lt;/b&gt; 락을 획득한 스레드만이 그 락을 해제할 수 있다. 스레드 A가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt;를 호출했다면, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt;도 반드시 스레드 A에서 호출되어야 한다. 다른 스레드가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt;을 호출하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationLockException&lt;/code&gt;이 던져진다.&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;은 이 모델을 깨뜨릴 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/bvIb5U/dJMcagen9ti/AAAAAAAAAAAAAAAAAAAAAMfGNnzl-l4hN9_fczErw3XpbioK6Yx0qq-dPFaeBzaN/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=lyNlwYrZwgaZBOx%2BUI4PsKOQQzw%3D&quot; alt=&quot;왜 lock 안의 await가 위험한가 &amp;mdash; 스레드가 바뀐다&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await SomeApiAsync()&lt;/code&gt; 지점에서 메서드는 일단 반환됩니다. API 응답이 도착해 콜백이 재개될 때, 그 콜백을 실행하는 스레드는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 라이브러리가 결정합니다 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThreadPool&lt;/code&gt; 스레드일 수도 있고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt;가 살아 있다면 원래 스레드일 수도 있지만 보장은 없습니다. &lt;b&gt;다른 스레드가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt;을 호출하면 즉시 예외가 나거나, 더 나쁘게는 락이 영원히 풀리지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;비동기 임계 구역의 정답 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt;&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt;은 스레드 소유권 개념이 없습니다. &quot;토큰 N개를 가진 풀&quot;이라는 모델이라, 어떤 스레드가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAsync&lt;/code&gt;로 토큰을 가져갔든 다른 스레드가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Release&lt;/code&gt;해도 됩니다. 그래서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 와 안전하게 결합할 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class AsyncCache
{
    private readonly SemaphoreSlim _gate = new(1, 1); // 카운트 1 = 사실상 mutex
    private string? _cached;

    public async Task&amp;lt;string&amp;gt; GetAsync(CancellationToken ct = default)
    {
        await _gate.WaitAsync(ct);   // ✓ 비동기 진입
        try
        {
            _cached ??= await FetchFromApiAsync(ct); // ✓ 안에서 await 가능
            return _cached;
        }
        finally
        {
            _gate.Release();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 차이:&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt;)&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;소유권 모델&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;스레드 단위&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;토큰 단위 (스레드 무관)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 가능&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;❌ 컴파일 에러&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✓ &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAsync&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;재진입&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;같은 스레드는 OK&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;같은 스레드여도 토큰 새로 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;무경합 비용&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;매우 낮음&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;보다 약간 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;퀴즈&lt;/b&gt; &amp;mdash; Unity에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await UnityWebRequest.SendWebRequest()&lt;/code&gt; 다음 줄로 돌아왔을 때 어떤 스레드인가? 답은 다음 글들에서 다루는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt; 챕터에 있지만, 한 줄로 말하면 &quot;기본 설정이라면 메인 스레드, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(false)&lt;/code&gt; 하면 풀 스레드&quot;입니다. &lt;b&gt;메인 스레드라도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;을 풀고 들어왔다는 보장은 없으니&lt;/b&gt; 답은 변하지 않습니다 &amp;mdash; 여전히 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 안의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;은 금지입니다.&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[데드락] 두 락을 다른 순서로 잡으면 영원히 풀리지 않는다&lt;/h2&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;private readonly object _lockA = new();
private readonly object _lockB = new();

void Worker1()
{
    lock (_lockA)
    {
        Thread.Sleep(10);   // Worker2가 _lockB 잡을 시간 확보
        lock (_lockB) { /* ... */ }  // _lockA 가진 채 _lockB를 기다림
    }
}

void Worker2()
{
    lock (_lockB)
    {
        Thread.Sleep(10);
        lock (_lockA) { /* ... */ }  // _lockB 가진 채 _lockA를 기다림
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Worker1&lt;/code&gt;은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;A &amp;rarr; B&lt;/code&gt; 순서로, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Worker2&lt;/code&gt;는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;B &amp;rarr; A&lt;/code&gt; 순서로 락을 잡습니다. 두 스레드가 거의 동시에 외부 락을 잡고 안쪽 락을 기다리면 &lt;b&gt;둘 다 영원히 대기&lt;/b&gt;합니다. 데드락입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/bNYsAn/dJMcagen9tj/AAAAAAAAAAAAAAAAAAAAAEjb93-uWSLOK6FE6qrc18RrhjCa0pliQEgxSJ5aFkoo/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=e7ulgv4xzsIBEvpVsyxzPpji93k%3D&quot; alt=&quot;데드락 &amp;mdash; 락 획득 순서가 뒤바뀌면 양쪽이 영원히 대기&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;데드락을 막는 두 가지 정석:&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;1) 락 획득 순서를 통일&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;모든 스레드가 &lt;b&gt;항상 같은 순서&lt;/b&gt;로 락을 잡도록 정합니다. 예를 들어 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_lockA&lt;/code&gt;를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_lockB&lt;/code&gt;보다 먼저 잡도록 코드 리뷰에서 강제합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;void Worker1() {
    lock (_lockA) lock (_lockB) { /* ... */ }   // A &amp;rarr; B
}
void Worker2() {
    lock (_lockA) lock (_lockB) { /* ... */ }   // A &amp;rarr; B (순서 통일)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2) &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.TryEnter&lt;/code&gt;로 타임아웃 도입&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드는 무한 대기만 지원합니다. 타임아웃이 필요하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.TryEnter&lt;/code&gt;를 직접 씁니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;bool taken = false;
try
{
    Monitor.TryEnter(_sync, TimeSpan.FromSeconds(2), ref taken);
    if (!taken)
    {
        Debug.LogWarning(&quot;락 획득 실패 &amp;mdash; 데드락 의심, 자원 반납&quot;);
        return;  // 다른 락도 풀고 후퇴 &amp;rarr; 데드락 회피
    }
    // 임계 구역
}
finally
{
    if (taken) Monitor.Exit(_sync);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;가장 좋은 데드락 대책은 &lt;b&gt;락을 두 개 동시에 잡지 않는 것&lt;/b&gt;입니다. 자료 구조 설계 단계에서 락의 영역을 좁게 잡고, 한 락 안에서 다른 락이 필요한 콜백을 호출하지 않습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[성능 특성] 무경합은 매우 싸고, 경합은 점점 비싸진다&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;Thin Lock &amp;rarr; Fat Lock 승격&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;CLR은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;이 거의 항상 무경합 상태에서 사용된다는 통계적 사실을 활용해 두 단계 구조를 씁니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/bgjUV8/dJMcajhPzk2/AAAAAAAAAAAAAAAAAAAAAHPmyKIEY6o5X3LuQ-w2vzia0KiTFYV54TB1Y7MgdrlK/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=IemCEYwjgDAjm01ste%2FYoOv%2F904%3D&quot; alt=&quot;Monitor의 두 단계 &amp;mdash; Thin Lock에서 Fat Lock으로&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;무경합 시 (Thin Lock):&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락이 비어 있으면 CLR은 객체 헤더의 sync 비트를 CAS(Compare-And-Swap) 한 번으로 잡습니다.&lt;/li&gt;
&lt;li&gt;같은 스레드가 다시 잡으면 재진입 카운트만 1 증가시키고 끝납니다.&lt;/li&gt;
&lt;li&gt;측정해 보면 한 번의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock { } enter/exit&lt;/code&gt;이 보통 &lt;b&gt;수십 나노초&lt;/b&gt; 수준입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_value++&lt;/code&gt; 한 줄을 보호하는 비용으로는 충분히 작습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;경합 시 (Fat Lock):&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 스레드가 점유 중이면 CLR은 짧은 시간 동안 spin-wait(busy loop)을 합니다. 락이 매우 짧게만 점유되는 흔한 경우라면 컨텍스트 스위치 없이 끝납니다.&lt;/li&gt;
&lt;li&gt;spin-wait 시간 안에 락이 풀리지 않으면 OS 커널의 동기화 객체를 사용해 &lt;b&gt;스레드를 sleep 시킵니다&lt;/b&gt;. 이때 객체의 SyncBlock이 별도로 할당되며 락은 &quot;fat lock&quot;으로 승격됩니다.&lt;/li&gt;
&lt;li&gt;일단 fat lock으로 승격되면 enter/exit 비용이 &lt;b&gt;마이크로초 단위로 늘어나고&lt;/b&gt;, 컨텍스트 스위치 비용까지 더해집니다. 한번 승격된 락은 보통 다시 thin으로 내려가지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;정리: 락은 짧게 머물수록 빠르다&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락 안에서 &lt;b&gt;I/O, 네트워크, 콘솔 출력&lt;/b&gt;을 하지 않습니다 &amp;mdash; 점유 시간을 ms 단위로 늘립니다.&lt;/li&gt;
&lt;li&gt;락 안에서 &lt;b&gt;이벤트 발생, 콜백 호출&lt;/b&gt;을 하지 않습니다 &amp;mdash; 외부 코드가 거기서 무엇을 할지 모르기 때문입니다 (또 다른 락을 잡으면 데드락).&lt;/li&gt;
&lt;li&gt;락 안에서 &lt;b&gt;할당이 큰 작업&lt;/b&gt;(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;new T[10000]&lt;/code&gt;)을 하지 않습니다.&lt;/li&gt;
&lt;li&gt;가능하면 &lt;b&gt;락 안의 작업은 한 두 줄&lt;/b&gt;로 끝나도록 자료 구조를 설계합니다. 락 밖에서 준비물을 모두 만들고, 락 안에서는 &quot;한 번에 바꿔치기&quot;만 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 8 ===================== --&gt;
&lt;h2 id=&quot;section-8&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[Unity 실전 패턴] 메인 스레드 모델과 lock의 사용처&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Transform&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GameObject&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MonoBehaviour&lt;/code&gt; 같은 엔진 객체를 &lt;b&gt;메인 스레드(Update 스레드)에서만&lt;/b&gt; 만질 수 있습니다. 백그라운드 스레드가 직접 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;transform.position = ...&lt;/code&gt;을 하면 즉시 던져지는 예외나, 더 나쁘게는 데이터 손상이 발생합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 Unity에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;이 등장하는 자리는 보통 다음 세 가지입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;1) 메인 스레드 디스패치 큐 (Action Queue)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;백그라운드 스레드(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;, 네트워크 콜백, 빌트인 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UnityWebRequest&lt;/code&gt;의 일부 콜백)에서 메인 스레드에 작업을 위임할 때 쓰는 큐입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class MainThreadDispatcher : MonoBehaviour
{
    private static MainThreadDispatcher _instance;
    private readonly Queue&amp;lt;Action&amp;gt; _queue = new();
    private readonly object _sync = new();

    public static void Enqueue(Action action)
    {
        // 어느 스레드에서 호출되든 안전해야 함
        lock (_instance._sync)
        {
            _instance._queue.Enqueue(action);
        }
    }

    private void Update()
    {
        // 매 프레임 메인 스레드에서 한 번씩 비웁니다
        while (true)
        {
            Action next;
            lock (_sync)
            {
                if (_queue.Count == 0) return;
                next = _queue.Dequeue();
            }
            // ⚠ 락 밖에서 실행 &amp;mdash; 콜백이 다시 Enqueue를 호출해도 데드락 없음
            try { next(); }
            catch (Exception e) { Debug.LogException(e); }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dequeue&lt;/code&gt;는 락 안에서, 실행(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;next()&lt;/code&gt;)은 락 밖에서&lt;/b&gt; 한다는 점입니다. 콜백이 무거울 수도 있고, 콜백 안에서 다시 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enqueue&lt;/code&gt;가 호출될 수도 있어서 락을 잡은 채 콜백을 부르면 위 두 시나리오 모두에서 막힙니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2) 백그라운드 스레드 풀에서의 객체 풀링&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;게임에서 자주 만들어지는 작은 데이터 객체(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;PathfindingResult&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;DamageEvent&lt;/code&gt; 등)를 GC 압박 없이 재사용하려면 풀이 필요합니다. 풀 자체는 여러 스레드가 동시에 빌리고 반납하므로 보호가 필요합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class ResultPool&amp;lt;T&amp;gt; where T : new()
{
    private readonly Stack&amp;lt;T&amp;gt; _stack = new();
    private readonly object _sync = new();

    public T Rent()
    {
        lock (_sync)
        {
            if (_stack.Count &amp;gt; 0) return _stack.Pop();
        }
        // 락 밖에서 new &amp;mdash; 객체 생성 비용이 락 점유 시간에 끼지 않음
        return new T();
    }

    public void Return(T item)
    {
        lock (_sync) { _stack.Push(item); }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;여기서도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;new T()&lt;/code&gt;를 &lt;b&gt;락 밖&lt;/b&gt;에서 한다는 점이 중요합니다. 풀 안의 데이터를 만지는 시간은 매우 짧고, 객체 생성은 GC가 끼어들 수 있는 비용 큰 작업입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3) 로깅 큐 (정확하지만 짧은 임계 구역)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;여러 스레드가 동시에 로그를 찍는 상황에서 파일 I/O를 직접 하면 충돌이 납니다. 큐에 모아 두고 별도 스레드가 비우는 패턴이 표준입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class LogBuffer
{
    private readonly Queue&amp;lt;string&amp;gt; _queue = new();
    private readonly object _sync = new();

    public void Add(string line)
    {
        lock (_sync) { _queue.Enqueue(line); }    // 짧다
    }

    public string[] Drain()
    {
        lock (_sync)
        {
            if (_queue.Count == 0) return Array.Empty&amp;lt;string&amp;gt;();
            var arr = _queue.ToArray();
            _queue.Clear();
            return arr;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Drain&lt;/code&gt;은 별도 스레드가 1초에 한 번 돌면서 호출하고, 받은 배열을 락 밖에서 디스크에 씁니다. 락 점유 시간이 큐의 크기와 무관하지는 않지만 메모리 복사 한 번이라 매우 짧습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;Unity에서 피해야 할 패턴&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;sqf&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ 메인 스레드 객체를 백그라운드에서 lock으로 보호한다고 안전해지지 않음
lock (_sync) { transform.position = newPos; }
// &amp;rarr; Unity는 transform 접근 자체를 메인 스레드에서만 허용. lock으로는 풀리지 않음.

// ❌ static 라이브러리(예: Resources)를 lock으로 직접 보호
lock (typeof(Resources)) { ... }
// &amp;rarr; Resources는 Unity 내부에서 자체 동기화. 외부 lock은 의미 없고 데드락 위험만 큼.

// ❌ Coroutine 안에서 lock 잡고 yield return WaitForSeconds
IEnumerator Foo() {
    lock (_sync) {
        yield return new WaitForSeconds(1); // &amp;larr; yield는 컴파일러에서 메서드를 쪼갠다.
    }                                       //    yield 이후 부분은 다음 프레임에 다시 호출되며,
}                                           //    그 사이 lock 상태는 정의되지 않음. 사실상 await의 친척.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;코루틴은 메인 스레드에서만 실행되므로 보통 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;이 필요 없습니다. 락이 필요하다면 그건 다른 스레드와 데이터를 주고받는 부분 &amp;mdash; 그 부분은 코루틴이 아니라 일반 메서드여야 합니다.&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 9 ===================== --&gt;
&lt;h2 id=&quot;section-9&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[다른 동기화 도구와의 비교] lock이 답이 아닌 경우&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;상황&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;도구&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;단순 카운터&amp;middot;플래그 증감&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 없이 CPU 명령어 한 두 개로 처리. lock보다 5~10배 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;비동기 코드의 임계 구역&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt;&lt;/b&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAsync&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;스레드 소유권 없음 &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;읽기 많고 쓰기 적은 캐시&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ReaderWriterLockSlim&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;동시 읽기를 허용해 처리량 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;임계 구역이 매우 짧고 경합 거의 없음&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SpinLock&lt;/code&gt;&lt;/b&gt; (전문가용)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;커널 진입 회피, 단 잘못 쓰면 CPU 낭비&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;한 시점에 N개 스레드 허용&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim(N, N)&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;자원 풀 모델&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;여러 줄을 묶어 일관성 보장&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;가장 단순하고 안전&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Interlocked vs lock&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_counter++&lt;/code&gt; 같은 단일 연산이라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.Increment(ref _counter)&lt;/code&gt;가 정답입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;if (_count &amp;gt; 0) _count--&lt;/code&gt; 처럼 &quot;검사 후 수정&quot;이 두 줄에 걸쳐 있다면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.CompareExchange&lt;/code&gt; 루프가 필요합니다.&lt;/blockquote&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;ConcurrentQueue vs lock + Queue&lt;/b&gt; 단순히 큐를 보호하는 거라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code&gt;가 락 없이 (대부분의 경우) 동작합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock + Queue&amp;lt;T&amp;gt;&lt;/code&gt; 패턴은 &quot;큐 외에도 여러 필드를 같이 일관되게 바꿔야 할 때&quot; 가치가 있습니다.&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 10 ===================== --&gt;
&lt;h2 id=&quot;section-10&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[자주 하는 실수와 해법]&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;실수&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;증상&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;해법&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(this)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;외부에서 락 탈취 가능, 디버깅 불가 데드락&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly object _sync&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(&quot;문자열 리터럴&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;모르는 라이브러리와 락 공유&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;같음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 객체를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;new&lt;/code&gt;로 매번 생성&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;동기화가 전혀 안 됨&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;객체를 필드로 한 번만 만들기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;CS1996 컴파일 에러&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim.WaitAsync&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 안에서 이벤트/콜백 호출&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;외부 코드가 다른 락을 잡으면 데드락&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 밖에서 호출, 락 안에서는 데이터만 복사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 객체가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;static&lt;/code&gt;이 아닌데 정적 데이터 보호&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;인스턴스마다 락이 달라 동기화 실패&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private static readonly object _staticSync&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Thread.Sleep&lt;/code&gt; / I/O&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;다른 스레드들이 길게 대기, 처리량 급락&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;I/O는 락 밖, 락 안에선 메모리 작업만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;두 락을 다른 순서로 획득&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;데드락&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;락 순서 통일 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.TryEnter&lt;/code&gt; 타임아웃&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;이 필요 없는데 그냥 다 거는 습관&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;처리량 저하, 무경합이라도 비용 발생&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;진짜 공유 상태인지 먼저 따져 보기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 11 ===================== --&gt;
&lt;h2 id=&quot;section-11&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[버전별 변화]&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;C# 1.0 (2002)&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 키워드 도입. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/finally&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt; 로 단순 변환.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;C# 4.0 (2010)&lt;/b&gt;: 컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enter(object, ref bool lockTaken)&lt;/code&gt; 오버로드를 사용하도록 변경. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Enter&lt;/code&gt; 호출 직후 비동기 예외(예: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThreadAbortException&lt;/code&gt;)가 발생해도 락 상태가 정확히 추적되도록 안전성이 강화됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;C# 5.0 (2012)&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 도입과 함께 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 본문에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 사용 시 컴파일러 에러(CS1996)가 명시화됨. 비동기 임계 구역에는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim.WaitAsync&lt;/code&gt; 권장.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.NET 8&lt;/b&gt;: 64비트 타깃에서 객체 헤더가 변경되며 thin lock 인코딩이 미세 조정됨. 사용자 입장에선 큰 차이 없음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.NET 9 / C# 13 (2024)&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Threading.Lock&lt;/code&gt; 타입 도입. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt; 문이 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;object&lt;/code&gt; 대신 이 새 타입을 받으면 컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Lock.EnterScope()&lt;/code&gt; 기반의 새로운 패턴으로 변환해 더 빠르고 더 안전한 동기화를 제공한다. &lt;b&gt;다음 글(13)에서 자세히 다룬다.&lt;/b&gt; 단, 이 글에서 살펴본 고전 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock(object)&lt;/code&gt; 패턴은 .NET 9 이후에도 그대로 유효하며, 기존 코드를 바꿀 필요는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 12 ===================== --&gt;
&lt;h2 id=&quot;section-12&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[요약]&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock (obj)&lt;/code&gt;는 컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Enter&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exit&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/finally&lt;/code&gt;로 풀어 주는 문법 설탕이다. IL 레벨에서는 메서드 호출 두 개와 try 블록 하나일 뿐이다.&lt;/li&gt;
&lt;li&gt;락 객체는 항상 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;private readonly object _sync = new();&lt;/code&gt;&lt;/b&gt; 형태로 선언한다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;this&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;typeof(X)&lt;/code&gt;, 문자열 리터럴은 외부와 락을 공유하게 만들어 데드락의 원인이 된다.&lt;/li&gt;
&lt;li&gt;락 안에서는 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 금지&lt;/b&gt;다 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor&lt;/code&gt;는 스레드 소유권 모델이라 콜백을 다른 스레드가 받으면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Monitor.Exit&lt;/code&gt;이 실패한다. 비동기에는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim.WaitAsync&lt;/code&gt;를 쓴다.&lt;/li&gt;
&lt;li&gt;락 안에서는 &lt;b&gt;짧게 머문다&lt;/b&gt; &amp;mdash; I/O, 콜백, 이벤트, 큰 할당을 락 안에서 하지 않는다. 무경합이면 매우 싸지만, 한 번 fat lock으로 승격되면 비용이 마이크로초 단위로 뛴다.&lt;/li&gt;
&lt;li&gt;Unity에서는 메인 스레드 디스패치 큐, 객체 풀, 로깅 큐 같은 &lt;b&gt;워커-메인 사이의 데이터 채널&lt;/b&gt;에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;을 쓴다. Unity 엔진 객체 자체는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;으로 보호되지 않는다 &amp;mdash; 메인 스레드에서만 만져야 한다.&lt;/li&gt;
&lt;li&gt;단순 카운터는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt;, 비동기는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SemaphoreSlim&lt;/code&gt;, 읽기 위주는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ReaderWriterLockSlim&lt;/code&gt; &amp;mdash; 상황에 맞는 도구를 고른다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;lock&lt;/code&gt;은 &quot;여러 줄을 묶어 일관성을 보장&quot;하는 자리에 가장 잘 맞는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>C#</category>
      <category>기초</category>
      <category>입문</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/582</guid>
      <comments>https://everyday-devup.tistory.com/582#entry582comment</comments>
      <pubDate>Sat, 9 May 2026 00:41:58 +0900</pubDate>
    </item>
    <item>
      <title>[PART13.비동기와 스레딩 기초(11/15)] 비동기 스트림 &amp;mdash; IAsyncEnumerable&amp;lt;T&amp;gt; &amp;middot; await foreach (C# 8)</title>
      <link>https://everyday-devup.tistory.com/581</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(11/15)] 비동기 스트림 &amp;mdash; IAsyncEnumerable&amp;lt;T&amp;gt; &amp;middot; await foreach (C# 8)&lt;/h1&gt;
&lt;p style=&quot;color: #868e96; font-size: 14px; margin: 0 0 32px; line-height: 1.6;&quot; data-ke-size=&quot;size16&quot;&gt;한 번에 한 건씩 비동기로 흘려보내는 데이터 스트림 / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;yield return&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;의 결합 / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;[EnumeratorCancellation]&lt;/code&gt;으로 안전하게 멈추는 법&lt;/p&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;[문제 제기] 1만 건의 로그를 모두 메모리에 올리고 시작할 것인가&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;[개념 정의] 비동기 Pull 모델 &amp;mdash; 한 건씩 당겨오는 스트림&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;[내부 동작] 컴파일러가 만드는 상태머신 &amp;mdash; `MoveNextAsync()`와 `Current`&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;[실전 적용] Before/After &amp;mdash; Unity 핫패스에서 메모리&amp;middot;반응성 모두 잡기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;[함정과 주의사항] 신입이 자주 틀리는 4가지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;[C# 버전별 변화] C# 8 도입부터 .NET 9까지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;[정리] 비동기 스트림 핵심 체크리스트&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[문제 제기] 1만 건의 로그를 모두 메모리에 올리고 시작할 것인가&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 모바일 게임 클라이언트에서 서버의 운영 로그 파일을 한 줄씩 화면에 표시한다고 상상해 봅시다. 로그가 1만 줄이라면, 우리는 흔히 이렇게 씁니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Unity 화면: &quot;로그 보기&quot; 버튼을 누르면 로그를 가져와 표시
public async Task LoadLogsAsync()
{
    List&amp;lt;string&amp;gt; lines = await ReadAllLinesAsync(path); // 1만 줄을 한꺼번에 List에 담기
    foreach (var line in lines)
    {
        AppendToScrollView(line);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드의 문제는 두 가지입니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;첫 줄이 화면에 뜨기까지 1만 줄이 모두 도착할 때까지 기다린다.&lt;/b&gt; 사용자는 그동안 빈 화면을 봅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;List&amp;lt;string&amp;gt; 1만 개가 한꺼번에 힙에 할당된다.&lt;/b&gt; Boehm GC(Unity의 가비지 컬렉터)가 이걸 회수하느라 GC 스파이크가 튀고, 모바일에서 프레임 드랍이 일어납니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;같은 문제가 채팅 메시지 구독, 네트워크 응답 스트리밍, 센서 데이터 수신 등 &lt;b&gt;&quot;끝없이 도착하는 데이터&quot;&lt;/b&gt; 전반에서 발생합니다. 우리는 데이터가 &lt;b&gt;준비되는 즉시 한 건씩 처리&lt;/b&gt;하면서도, 각 데이터가 도착하기까지의 &lt;b&gt;대기 시간 동안 스레드를 놓아주는&lt;/b&gt; 방법이 필요합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 8.0이 답을 줍니다 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;. 이 글에서는 이 둘이 컴파일러에 의해 어떤 상태머신으로 변환되는지, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;[EnumeratorCancellation]&lt;/code&gt;으로 어떻게 안전하게 취소하는지, Unity 핫패스에서 어떻게 적용해야 하는지를 IL 레벨까지 파헤쳐 봅니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[개념 정의] 비동기 Pull 모델 &amp;mdash; 한 건씩 당겨오는 스트림&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;동기 Pull, 비동기 Pull, 비동기 Push의 차이&lt;/h3&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; &amp;mdash; 비동기 메서드 (Asynchronous method)&lt;/b&gt; 메서드 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;를 사용할 수 있게 해주는 키워드. 컴파일러가 이 메서드를 상태머신으로 변환해 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 지점에서 스레드를 놓아주고, 결과가 준비되면 다시 이어서 실행한다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&amp;lt;int&amp;gt; FetchAsync() { ... }&lt;/code&gt; 호출자에게 즉시 Task를 돌려주고, 메서드 본문은 비동기로 실행된다.&lt;/blockquote&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;yield return&lt;/code&gt; &amp;mdash; 반복기 반환 (Iterator return)&lt;/b&gt; 메서드를 한 번에 끝내지 않고, 호출자가 다음 값을 요청할 때마다 한 건씩 값을 돌려주는 구문. 컴파일러가 메서드를 반복기 상태머신으로 변환해 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;yield return&lt;/code&gt; 지점에서 잠시 멈추고, 다음 호출 때 그 자리부터 이어서 실행한다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IEnumerable&amp;lt;int&amp;gt; Numbers() { yield return 1; yield return 2; }&lt;/code&gt; Numbers()를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;foreach&lt;/code&gt;로 돌면 1, 2를 차례로 받는다.&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;는 위 두 키워드(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;yield return&lt;/code&gt;)를 한 메서드 안에서 동시에 쓸 수 있게 해줍니다. 즉 &lt;b&gt;비동기로 한 건씩 값을 흘려보내는 메서드&lt;/b&gt;를 만들 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/TZeRz/dJMcadu7ioP/AAAAAAAAAAAAAAAAAAAAACaVb3_7phbRecmb2wefFrQBo_y_xVW1YymrNsfrJ7FT/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=cQTd2sJoGBTm2POgwaqUTKL3vbQ%3D&quot; alt=&quot;데이터 시퀀스의 세 가지 모델&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;소비자가 다음 값을 원할 때만&lt;/b&gt; 생산자가 한 발짝 움직인다는 점입니다. 이를 &lt;b&gt;풀(Pull) 기반 모델&lt;/b&gt;이라 부르고, 소비자 처리 속도가 곧 생산자 속도가 되므로 &lt;b&gt;백프레셔(Backpressure, 생산 속도가 소비 속도를 넘을 때 데이터가 쌓이는 압력)&lt;/b&gt; 가 자연스럽게 제어됩니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;가장 단순한 예시 &amp;mdash; 한 줄씩 파일 읽기&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;문제 제기에서 제기한 1만 줄 로그 문제를 비동기 스트림으로 풀면 다음과 같습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

public class LogReader
{
    // 생산자: 한 줄씩 비동기로 흘려보낸다
    public static async IAsyncEnumerable&amp;lt;string&amp;gt; ReadLinesAsync(string path)
    {
        using var reader = new StreamReader(path);
        string? line;
        while ((line = await reader.ReadLineAsync()) is not null)
        {
            yield return line; // 한 줄 도착 즉시 소비자에게 전달
        }
    }

    // 소비자: 한 줄 도착 즉시 화면에 추가
    public static async Task DisplayAsync(string path)
    {
        await foreach (var line in ReadLinesAsync(path))
        {
            System.Console.WriteLine(line);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ReadLinesAsync&lt;/code&gt;는 한 줄을 비동기로 읽고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;yield return&lt;/code&gt;으로 한 줄 토해내고, 다음 호출이 오기 전까지 멈춰 있습니다. 호출자(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;)는 한 줄을 받자마자 화면에 추가하고, 다 처리하면 다음 줄을 요청합니다. &lt;b&gt;첫 줄은 1만 줄이 다 도착하기 전에 이미 화면에 떠 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;기술 정의로는 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync()&lt;/code&gt;(다음 값 요청)와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Current&lt;/code&gt;(현재 값) 두 멤버를 가진 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerator&amp;lt;T&amp;gt;&lt;/code&gt;를 만들어주는 인터페이스이고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;는 컴파일러가 이 둘을 호출하는 비동기 루프로 변환해주는 구문입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;같은 메서드의 IL &amp;mdash; 상태머신이 따로 만들어진다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;위 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ReadLinesAsync&lt;/code&gt;처럼 메서드를 짜면, 컴파일러는 &lt;b&gt;두 개의 별도 클래스 + 한 개의 외부 메서드&lt;/b&gt;를 생성합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;groovy&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 생산자 메서드 자체는 사실상 wrapper다 &amp;mdash; 진짜 코드는 상태머신에 있다
.method public hidebysig static class System.Collections.Generic.IAsyncEnumerable`1&amp;lt;string&amp;gt;
    ReadLinesAsync(string path) cil managed
{
    // 컴파일러가 만든 상태머신 인스턴스를 반환만 한다
    .custom instance void System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute::.ctor(...) = (...)
    newobj instance void '&amp;lt;ReadLinesAsync&amp;gt;d__0'::.ctor(int32) // &amp;larr; 상태머신 생성
    ret
}

// 컴파일러가 자동 생성한 중첩 클래스 (이게 핵심)
.class nested private auto ansi sealed beforefieldinit '&amp;lt;ReadLinesAsync&amp;gt;d__0'
    extends System.Object
    implements
        IAsyncEnumerable`1&amp;lt;string&amp;gt;,
        IAsyncEnumerator`1&amp;lt;string&amp;gt;,    // &amp;larr; 둘 다 구현
        IAsyncDisposable,
        IAsyncStateMachine
{
    .field public int32 '&amp;lt;&amp;gt;1__state'                        // 상태 머신의 현재 단계
    .field public AsyncIteratorMethodBuilder '&amp;lt;&amp;gt;t__builder' // 비동기 빌더 (Task용 빌더와 다른 전용 빌더)
    .field public ManualResetValueTaskSourceCore`1&amp;lt;bool&amp;gt;
        '&amp;lt;&amp;gt;v__promiseOfValueOrEnd'                          // ValueTask&amp;lt;bool&amp;gt;의 백킹
    .field private string '&amp;lt;&amp;gt;2__current'                    // Current 속성 값
    .field private bool '&amp;lt;&amp;gt;w__disposeMode'                  // DisposeAsync 호출됐는지 표시
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설:&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncIteratorMethodBuilder&lt;/code&gt; &amp;mdash; 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt; 메서드가 쓰는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&lt;/code&gt;와 &lt;b&gt;다른 전용 빌더&lt;/b&gt;입니다. 비동기 반복기 전용으로, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync()&lt;/code&gt;마다 한 번씩 결과(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;true&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;false&lt;/code&gt;)를 돌려주는 구조에 맞춰져 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ManualResetValueTaskSourceCore&amp;lt;bool&amp;gt;&lt;/code&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;bool&amp;gt;&lt;/code&gt;을 &lt;b&gt;재사용 가능하게&lt;/b&gt; 만드는 값 형식 백엔드입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync()&lt;/code&gt;가 매번 새 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;를 만들어 힙에 할당하는 대신, 같은 인스턴스를 리셋해 재사용합니다. 이 한 줄이 비동기 스트림의 GC 효율을 결정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;'&amp;lt;&amp;gt;w__disposeMode'&lt;/code&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt; 루프가 중간에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;break&lt;/code&gt;하거나 예외가 나면, 컴파일러가 이 플래그를 켜고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync()&lt;/code&gt;를 한 번 더 호출해 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 블록의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;까지 비동기적으로 정리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[내부 동작] 컴파일러가 만드는 상태머신 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync()&lt;/code&gt;와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Current&lt;/code&gt;&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;동작 흐름 한눈에&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/cwDPmP/dJMcaaSMCQr/AAAAAAAAAAAAAAAAAAAAACo0F3mDt52WTaKkxs5r4rrhKMBGLly1PTq5PhTndhDs/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=hYGazb3uTZHBlF%2BG0WtGig1FTI4%3D&quot; alt=&quot;await foreach가 컴파일러에 의해 변환되는 흐름&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;컴파일러는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;를 어떻게 풀어쓰는가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 코드와 컴파일 후 동등한 코드를 나란히 봅시다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 우리가 쓴 코드
await foreach (var line in ReadLinesAsync(path))
{
    Console.WriteLine(line);
}

// 컴파일러가 변환한 코드 (개념적으로 동등)
var enumerator = ReadLinesAsync(path).GetAsyncEnumerator(default);
try
{
    while (await enumerator.MoveNextAsync())
    {
        var line = enumerator.Current;
        Console.WriteLine(line);
    }
}
finally
{
    await enumerator.DisposeAsync();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 변환의 핵심은 세 가지입니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await enumerator.MoveNextAsync()&lt;/code&gt; &amp;mdash; 한 건씩 다음 값을 요청하는 비동기 호출. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;bool&amp;gt;&lt;/code&gt;을 반환하므로 동기 완료 시 힙 할당이 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;enumerator.Current&lt;/code&gt; &amp;mdash; 동기 속성. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync()&lt;/code&gt;가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;true&lt;/code&gt;를 반환한 직후에만 유효합니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await enumerator.DisposeAsync()&lt;/code&gt; &amp;mdash; 루프가 끝나거나 중간에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;break&lt;/code&gt;/예외로 빠져도 &lt;b&gt;비동기적으로&lt;/b&gt; 자원을 정리합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;실제 IL &amp;mdash; 소비자(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;) 측 분석&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;소비자 메서드는 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt; 메서드와 똑같이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncStateMachine&lt;/code&gt; 상태머신으로 변환됩니다. 다만 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerator&amp;lt;T&amp;gt;&lt;/code&gt;를 호출한다는 점이 다릅니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 소비자 측 C# 원본
public static async Task ConsumeAsync()
{
    await foreach (var n in GenerateAsync())
    {
        Console.WriteLine(n);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 컴파일러가 만든 ConsumeAsync의 상태머신 MoveNext (핵심만 발췌)
.class nested private auto ansi sealed beforefieldinit '&amp;lt;ConsumeAsync&amp;gt;d__1'
    extends System.ValueType
    implements IAsyncStateMachine
{
    .field public int32 '&amp;lt;&amp;gt;1__state'
    .field public AsyncTaskMethodBuilder '&amp;lt;&amp;gt;t__builder'
    .field private IAsyncEnumerator`1&amp;lt;int32&amp;gt; '&amp;lt;&amp;gt;7__wrap1'  // &amp;larr; 열거자 보관
    // ...
}

// MoveNext 본문
IL_0012: call class IAsyncEnumerable`1&amp;lt;int32&amp;gt; BasicStream::GenerateAsync()
IL_0020: callvirt instance class IAsyncEnumerator`1&amp;lt;int32&amp;gt;
         IAsyncEnumerable`1&amp;lt;int32&amp;gt;::GetAsyncEnumerator(CancellationToken)  // ① 열거자 생성
IL_0044: callvirt instance int32 IAsyncEnumerator`1&amp;lt;int32&amp;gt;::get_Current()  // ② Current
IL_0049: call    void System.Console::WriteLine(int32)
IL_0054: callvirt instance ValueTask`1&amp;lt;bool&amp;gt;
         IAsyncEnumerator`1&amp;lt;int32&amp;gt;::MoveNextAsync()                        // ③ 다음 값 요청
IL_005c: call    instance ValueTaskAwaiter`1&amp;lt;bool&amp;gt; ValueTask`1&amp;lt;bool&amp;gt;::GetAwaiter()
IL_0064: call    instance bool ValueTaskAwaiter`1&amp;lt;bool&amp;gt;::get_IsCompleted() // 동기 완료 검사
IL_006b: ldarg.0
// ... await 지점에 멈췄다가 재개되면 &amp;darr;
IL_00aa: call    instance bool ValueTaskAwaiter`1&amp;lt;bool&amp;gt;::GetResult()
IL_00b1: brtrue.s IL_003e                                                  // true면 다시 Current로
// 루프 종료 후 finally 블록에서:
IL_00cf: callvirt instance ValueTask System.IAsyncDisposable::DisposeAsync() // ④ 비동기 정리&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설:&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;callvirt ... GetAsyncEnumerator(CancellationToken)&lt;/code&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;는 인터페이스이므로 가상 호출(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;callvirt&lt;/code&gt;)로 해석됩니다. 매개변수로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;을 받는 점에 주목하세요. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WithCancellation(token)&lt;/code&gt;을 쓰면 이 자리에 토큰이 들어갑니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;callvirt ... MoveNextAsync() &amp;rarr; ValueTask&amp;lt;bool&amp;gt;&lt;/code&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;bool&amp;gt;&lt;/code&gt;이 아니라 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;bool&amp;gt;&lt;/code&gt;입니다. 동기 완료 시(예: 캐시된 다음 값이 즉시 준비된 경우) 힙 할당이 0이 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncDisposable::DisposeAsync()&lt;/code&gt; &amp;mdash; 컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally&lt;/code&gt;에 자동으로 끼워 넣어, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 블록의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;까지 &lt;b&gt;비동기로&lt;/b&gt; 호출합니다. 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IDisposable.Dispose()&lt;/code&gt;와는 다른 인터페이스이므로 자원 정리도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;생산자 측 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncIteratorMethodBuilder&lt;/code&gt;의 정체&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;생산자(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; 메서드) 쪽 IL은 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt;와 다른 빌더를 씁니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;zephir&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 생산자 측 IL (핵심만)
.class nested private auto ansi sealed '&amp;lt;GenerateAsync&amp;gt;d__0'
    extends System.Object
    implements
        IAsyncEnumerable`1&amp;lt;int32&amp;gt;,
        IAsyncEnumerator`1&amp;lt;int32&amp;gt;,
        IAsyncDisposable,
        IAsyncStateMachine
{
    .field public AsyncIteratorMethodBuilder '&amp;lt;&amp;gt;t__builder'                      // &amp;larr; 전용 빌더
    .field public ManualResetValueTaskSourceCore`1&amp;lt;bool&amp;gt; '&amp;lt;&amp;gt;v__promiseOfValueOrEnd' // &amp;larr; ValueTask 백엔드
    .field private int32 '&amp;lt;&amp;gt;2__current'                                          // &amp;larr; Current 값
    .field private bool '&amp;lt;&amp;gt;w__disposeMode'                                       // &amp;larr; 정리 모드 플래그
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설:&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncIteratorMethodBuilder&lt;/code&gt; &amp;mdash; 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt;의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&lt;/code&gt;와 다릅니다. 한 번 시작하고 끝나는 게 아니라, &lt;b&gt;여러 번&lt;/b&gt; &quot;다음 값&quot; 요청에 응답해야 하므로 전용 빌더가 필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ManualResetValueTaskSourceCore&amp;lt;bool&amp;gt;&lt;/code&gt; &amp;mdash; 매 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync()&lt;/code&gt; 호출마다 새 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 객체를 만들지 않고, &lt;b&gt;이미 만들어둔 ValueTask 백엔드 인스턴스를 리셋해 재사용&lt;/b&gt;합니다. 이게 비동기 스트림이 GC를 거의 안 쓰는 이유입니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;'&amp;lt;&amp;gt;w__disposeMode'&lt;/code&gt; &amp;mdash; 소비자가 루프를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;break&lt;/code&gt;하거나 예외로 빠지면, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;DisposeAsync()&lt;/code&gt;가 이 플래그를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;true&lt;/code&gt;로 켜고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync&lt;/code&gt;를 한 번 더 호출합니다. 그러면 상태머신이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt;의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-finally&lt;/code&gt;의 정리 코드를 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;요약하면 &amp;mdash; 비동기 스트림은 &lt;b&gt;두 종류의 상태머신&lt;/b&gt;이 만나는 구조입니다. 생산자는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncIteratorMethodBuilder&lt;/code&gt;로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync&lt;/code&gt; 호출마다 한 번씩 깨어났다 다시 자고, 소비자는 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 상태머신으로 그 결과를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;합니다. 둘 모두 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt; 기반이므로 정상 경로에서 힙 할당이 거의 없습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[실전 적용] Before/After &amp;mdash; Unity 핫패스에서 메모리&amp;middot;반응성 모두 잡기&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;사례 1 &amp;mdash; 대용량 데이터를 List에 모으기 vs 한 건씩 흘리기&lt;/h3&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;Before &amp;mdash; 1000개를 List에 모아 한꺼번에 반환&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity에서 서버에서 캐릭터 인벤토리 1000개를 받아 UI에 표시하는 상황이라고 합시다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Before: 전부 List에 담아 반환 &amp;mdash; 첫 화면이 늦고 GC 압박이 크다
public static async Task&amp;lt;List&amp;lt;int&amp;gt;&amp;gt; ReadAllAsync()
{
    var result = new List&amp;lt;int&amp;gt;();
    for (int i = 0; i &amp;lt; 1000; i++)
    {
        await Task.Yield();   // 비동기 작업 시뮬레이션
        result.Add(i);
    }
    return result;
}

// 호출자
List&amp;lt;int&amp;gt; all = await ReadAllAsync();   // 1000개 다 모일 때까지 기다림
foreach (var x in all) UseInUI(x);       // 그 다음에야 UI 갱신 시작&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Before의 IL (발췌)
IL_0001: newobj instance void List`1&amp;lt;int32&amp;gt;::.ctor()         // &amp;larr; 힙에 List 1개 할당
.locals init ([0] valuetype '&amp;lt;ReadAllAsync&amp;gt;d__0', ...)
// 루프 1000회 동안 List에 Add
IL_004f: callvirt instance void List`1&amp;lt;int32&amp;gt;::Add(!0)
// 마지막에 List 통째로 SetResult
IL_0094: call instance void AsyncTaskMethodBuilder`1&amp;lt;List`1&amp;lt;int32&amp;gt;&amp;gt;::SetResult(!0)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: 마지막 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SetResult&lt;/code&gt;가 호출될 때까지 호출자는 한 줄도 받지 못합니다. 그동안 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;List&amp;lt;int&amp;gt;&lt;/code&gt;가 1000개 항목을 담느라 내부 배열이 여러 번 재할당되고(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Capacity&lt;/code&gt; 증가 시 새 배열로 복사), 모두 힙에 머뭅니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;After &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;int&amp;gt;&lt;/code&gt;로 한 건씩 흘리기&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Runtime.CompilerServices;
using System.Threading;

// After: 한 건씩 흘려보낸다 &amp;mdash; 첫 항목 즉시 사용 가능, GC 압박 적음
public static async IAsyncEnumerable&amp;lt;int&amp;gt; ReadStreamAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    for (int i = 0; i &amp;lt; 1000; i++)
    {
        ct.ThrowIfCancellationRequested();
        await Task.Yield();
        yield return i;
    }
}

// 호출자
await foreach (var x in ReadStreamAsync().WithCancellation(token))
{
    UseInUI(x);   // 첫 값 즉시 사용
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// After의 IL (생산자 측 발췌)
.class nested '&amp;lt;ReadStreamAsync&amp;gt;d__0'
    implements IAsyncEnumerable`1&amp;lt;int32&amp;gt;, IAsyncEnumerator`1&amp;lt;int32&amp;gt;, IAsyncDisposable, IAsyncStateMachine
{
    .field public AsyncIteratorMethodBuilder '&amp;lt;&amp;gt;t__builder'
    .field public ManualResetValueTaskSourceCore`1&amp;lt;bool&amp;gt; '&amp;lt;&amp;gt;v__promiseOfValueOrEnd'  // &amp;larr; ValueTask 재사용
    .field private int32 '&amp;lt;&amp;gt;2__current'
    .field public CancellationToken cancellationToken                                // &amp;larr; 취소 토큰 보관
}

IL_0007: call valuetype AsyncIteratorMethodBuilder
         AsyncIteratorMethodBuilder::Create()                  // &amp;larr; 일반 async와 다른 전용 빌더
IL_000c: stfld AsyncIteratorMethodBuilder ...::'&amp;lt;&amp;gt;t__builder'

// MoveNext 본문 &amp;mdash; yield return마다 ValueTask 백엔드를 SetResult로 깨운다
IL_010e: ldflda ManualResetValueTaskSourceCore`1&amp;lt;bool&amp;gt; ...::'&amp;lt;&amp;gt;v__promiseOfValueOrEnd'
IL_0113: ldc.i4.1
IL_0115: call instance void ManualResetValueTaskSourceCore`1&amp;lt;bool&amp;gt;::SetResult(!0)  // 다음 값 도착 신호&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;List&amp;lt;int&amp;gt;&lt;/code&gt; 인스턴스가 통째로 사라졌습니다. 그 자리를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncIteratorMethodBuilder&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ManualResetValueTaskSourceCore&amp;lt;bool&amp;gt;&lt;/code&gt; 1쌍이 차지하는데, 이 둘은 모두 &lt;b&gt;값 형식&lt;/b&gt;이라 상태머신 구조체 안에 인라인으로 올라갑니다. 1000번의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync&lt;/code&gt; 호출 동안 힙 할당은 사실상 상태머신 박싱 1회뿐입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;사례 2 &amp;mdash; Unity 모바일 핫패스: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Resources.Load&lt;/code&gt;를 스트리밍으로&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 모바일에서 100개의 텍스처를 로드해 갤러리를 보여주는 상황입니다. 한꺼번에 로드하면 메모리 스파이크와 첫 화면 지연이 모두 발생합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class GalleryStreamer : MonoBehaviour
{
    // After: 한 장씩 비동기 스트리밍 (UniTask 또는 Awaitable 기반)
    public static async IAsyncEnumerable&amp;lt;Texture2D&amp;gt; StreamTexturesAsync(
        IReadOnlyList&amp;lt;string&amp;gt; paths,
        [EnumeratorCancellation] CancellationToken ct = default)
    {
        foreach (var path in paths)
        {
            ct.ThrowIfCancellationRequested();

            var request = Resources.LoadAsync&amp;lt;Texture2D&amp;gt;(path);
            while (!request.isDone)
            {
                await System.Threading.Tasks.Task.Yield(); // Unity의 메인 스레드 양보
            }

            if (request.asset is Texture2D tex)
            {
                yield return tex;
            }
        }
    }

    // 소비자: 갤러리 셀에 한 장 도착할 때마다 즉시 표시
    public async Task ShowGalleryAsync(IReadOnlyList&amp;lt;string&amp;gt; paths, CancellationToken ct)
    {
        int idx = 0;
        await foreach (var tex in StreamTexturesAsync(paths).WithCancellation(ct))
        {
            cells[idx++].SetTexture(tex); // 한 장 도착 즉시 화면 업데이트
        }
    }

    public Cell[] cells = default!;
    public class Cell : MonoBehaviour { public void SetTexture(Texture2D t) {} }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;gradle&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 컴파일된 StreamTexturesAsync의 상태머신 (핵심)
.class nested '&amp;lt;StreamTexturesAsync&amp;gt;d__0'
    implements IAsyncEnumerable`1&amp;lt;Texture2D&amp;gt;, IAsyncEnumerator`1&amp;lt;Texture2D&amp;gt;, IAsyncDisposable
{
    .field public IReadOnlyList`1&amp;lt;string&amp;gt; paths
    .field public CancellationToken cancellationToken           // &amp;larr; [EnumeratorCancellation]이 자동으로 채워줌
    .field private Texture2D '&amp;lt;&amp;gt;2__current'
    .field public AsyncIteratorMethodBuilder '&amp;lt;&amp;gt;t__builder'
}

// MoveNext에서 yield return 직전
IL_0093: ldarg.0
IL_0094: ldloc.s tex
IL_0096: stfld class Texture2D ...::'&amp;lt;&amp;gt;2__current'              // Current에 텍스처 1장 저장
IL_009b: ldarg.0
IL_009c: ldflda ManualResetValueTaskSourceCore`1&amp;lt;bool&amp;gt; ...::'&amp;lt;&amp;gt;v__promiseOfValueOrEnd'
IL_00a1: ldc.i4.1
IL_00a3: call instance void ManualResetValueTaskSourceCore`1&amp;lt;bool&amp;gt;::SetResult(!0)  // 소비자 깨우기
IL_00a8: ret                                                                       // MoveNext 종료&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: 한 장 로드 &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Current&lt;/code&gt;에 저장 &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SetResult&lt;/code&gt;로 소비자 깨움 &amp;rarr; MoveNext 즉시 종료. 다음 호출이 올 때까지 상태머신은 잠들어 있고, &lt;b&gt;모든 텍스처를 동시에 메모리에 올리지 않습니다&lt;/b&gt;. Unity의 IL2CPP(C++ 트랜스파일러) 환경에서도 동일한 상태머신 구조가 유지되며, 모바일에서 GC 스파이크가 크게 줄어듭니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;판단 기준 &amp;mdash; 언제 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;를 쓰는가&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;상황&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;추천&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;데이터 양이 작고(&amp;lt;100), 모두 모아 한꺼번에 처리&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;데이터 양이 크고(&amp;gt;1000), 한 건씩 처리 가능&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;끝없이 들어오는 이벤트(채팅, 센서, 웹소켓)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;페이지네이션 API &amp;mdash; 다음 페이지를 차례로 받음&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;여러 작업을 병렬로 시작해 결과 취합&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;최신 값만 중요 &amp;mdash; 누락 허용&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IObservable&amp;lt;T&amp;gt;&lt;/code&gt;(Rx)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[함정과 주의사항] 신입이 자주 틀리는 4가지&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정 1 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;로 착각하기&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ 잘못된 패턴: IEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt; &amp;mdash; 이름은 비슷하지만 전혀 다른 동작
public static IEnumerable&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt; GenerateBad()
{
    for (int i = 0; i &amp;lt; 10; i++)
    {
        yield return SomeAsync(i); // foreach 직후 모든 Task가 즉시 시작됨
    }
}

// 호출자 &amp;mdash; 10개 Task가 한꺼번에 fire되어 백엔드 폭격
foreach (var task in GenerateBad())
{
    var result = await task; // 이미 시작된 Task의 결과만 기다린다
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 잘못된 IEnumerable&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt;의 IL &amp;mdash; 일반 동기 반복기다
.method public hidebysig static class IEnumerable`1&amp;lt;Task`1&amp;lt;int32&amp;gt;&amp;gt;
    GenerateBad() cil managed
{
    newobj instance void '&amp;lt;GenerateBad&amp;gt;d__0'::.ctor(int32)  // &amp;larr; 동기 반복기
    ret
}
// 상태머신은 IEnumerator`1&amp;lt;Task`1&amp;lt;int32&amp;gt;&amp;gt;만 구현 &amp;mdash; IAsyncEnumerator 아님&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✅ 올바른 패턴: IAsyncEnumerable&amp;lt;int&amp;gt; &amp;mdash; 한 건씩 비동기로 순차 진행
public static async IAsyncEnumerable&amp;lt;int&amp;gt; GenerateGood()
{
    for (int i = 0; i &amp;lt; 10; i++)
    {
        var result = await SomeAsync(i); // 한 번에 하나씩 await
        yield return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 올바른 IAsyncEnumerable&amp;lt;int&amp;gt;의 IL
.method public hidebysig static class IAsyncEnumerable`1&amp;lt;int32&amp;gt;
    GenerateGood() cil managed
{
    newobj instance void '&amp;lt;GenerateGood&amp;gt;d__1'::.ctor(int32)
    ret
}
// 상태머신은 IAsyncEnumerable`1, IAsyncEnumerator`1, IAsyncDisposable, IAsyncStateMachine 모두 구현&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: 두 IL은 &lt;b&gt;반환 타입과 구현 인터페이스 자체가 다릅니다&lt;/b&gt;. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;는 동기로 즉시 모든 Task를 fire하는 hot task 컬렉션이고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;는 소비자가 다음을 요청할 때만 한 발짝 움직이는 cold stream입니다. 이름만 보고 같다고 착각하면 백엔드에 동시 요청 폭탄을 보냅니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정 2 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;을 그냥 매개변수로 받기&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ 잘못된 패턴: 그냥 CancellationToken을 받기만 함 &amp;mdash; WithCancellation()이 무용지물
public static async IAsyncEnumerable&amp;lt;int&amp;gt; ProduceBad(CancellationToken ct)
{
    for (int i = 0; i &amp;lt; 100; i++)
    {
        ct.ThrowIfCancellationRequested();
        await Task.Delay(100);
        yield return i;
    }
}

// 호출자
var cts = new CancellationTokenSource(500);
await foreach (var x in ProduceBad(default).WithCancellation(cts.Token))  // &amp;larr; 토큰이 ct로 전달되지 않음!
{
    Console.WriteLine(x); // 500ms 후에도 멈추지 않는다
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;actionscript&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 잘못된 코드의 상태머신 (해당 메서드의 매개변수 보관 필드)
.field public CancellationToken ct
// &amp;rarr; WithCancellation(token)으로 넘어온 토큰은 GetAsyncEnumerator(CancellationToken)으로
//   전달되지만, 컴파일러는 그것을 ct 필드에 자동으로 매핑할 줄 모른다.
//   ct에는 호출 시점의 default(빈 토큰)가 그대로 박혀 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✅ 올바른 패턴: [EnumeratorCancellation]을 붙여 컴파일러가 토큰을 주입하게 함
public static async IAsyncEnumerable&amp;lt;int&amp;gt; ProduceGood(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    for (int i = 0; i &amp;lt; 100; i++)
    {
        ct.ThrowIfCancellationRequested();
        await Task.Delay(100, ct);
        yield return i;
    }
}

// 호출자
var cts = new CancellationTokenSource(500);
await foreach (var x in ProduceGood().WithCancellation(cts.Token))  // &amp;larr; 토큰이 ct에 자동 주입됨
{
    Console.WriteLine(x); // 500ms 후 OperationCanceledException으로 정상 중단
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 올바른 코드의 매개변수 메타데이터
.param [1]
    .custom instance void EnumeratorCancellationAttribute::.ctor() = (...)  // &amp;larr; 핵심 어트리뷰트
    .field public CancellationToken ct

// GetAsyncEnumerator(CancellationToken token) 안에서:
//   if (token.CanBeCanceled) {
//       this.ct = CancellationTokenSource.CreateLinkedTokenSource(callerToken, token).Token;
//   }
// &amp;rarr; 컴파일러가 자동 생성한 코드가 호출자 토큰과 매개변수 토큰을 합쳐 ct에 주입&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;[EnumeratorCancellation]&lt;/code&gt; 어트리뷰트가 핵심입니다. 이게 붙은 매개변수 자리에만 컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WithCancellation(token)&lt;/code&gt;으로 받은 토큰을 자동 연결해줍니다. 어트리뷰트가 없으면 호출자가 토큰을 아무리 넘겨도 메서드 안에서는 빈 토큰만 보입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정 3 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt; 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;break&lt;/code&gt;하면 정리가 안 된다고 착각하기&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 흔한 오해: &quot;break하면 finally가 안 돌 거야&quot;
await foreach (var line in ReadLinesAsync(path))
{
    if (line.StartsWith(&quot;STOP&quot;)) break; // &amp;larr; 여기서 빠져나가면 파일이 안 닫히지 않을까?
    Process(line);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 컴파일러가 자동으로 만든 finally 블록
.try
{
    // await foreach 본문
    leave.s IL_END
}
finally
{
    callvirt instance ValueTask IAsyncDisposable::DisposeAsync()  // &amp;larr; 무조건 호출됨
    ...
    endfinally
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;는 컴파일러가 자동으로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-finally&lt;/code&gt;를 감싸 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;DisposeAsync()&lt;/code&gt;를 보장합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;break&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;return&lt;/code&gt;, 예외 어떤 경로로 빠져도 정리가 됩니다. 생산자 쪽 상태머신은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;'&amp;lt;&amp;gt;w__disposeMode' = true&lt;/code&gt;를 받고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNext&lt;/code&gt;를 한 번 더 돌려 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 블록의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;까지 처리합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정 4 &amp;mdash; Unity 메인 스레드 컨텍스트 무시 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(false)&lt;/code&gt; 누락)&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ 잘못된 패턴: 라이브러리 코드인데 메인 스레드 컨텍스트로 자꾸 돌아옴
public async IAsyncEnumerable&amp;lt;int&amp;gt; LibraryStreamBad()
{
    for (int i = 0; i &amp;lt; 1000; i++)
    {
        await Task.Delay(10); // ConfigureAwait 없음 &amp;rarr; 매번 동기화 컨텍스트 캡처/복귀 비용
        yield return i;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// IL에서는 ConfigureAwait 호출이 보이지 않음 &amp;rarr; 기본값(true)
IL_0019: call valuetype TaskAwaiter Task::GetAwaiter()
// 호출 컨텍스트(Unity 메인 스레드)가 캡처되어 매번 복귀&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✅ 올바른 패턴: 라이브러리에서는 ConfigureAwait(false)
public async IAsyncEnumerable&amp;lt;int&amp;gt; LibraryStreamGood()
{
    for (int i = 0; i &amp;lt; 1000; i++)
    {
        await Task.Delay(10).ConfigureAwait(false); // 컨텍스트 복귀 비용 제거
        yield return i;
    }
}

// 소비자 측에서도 한 번에 적용 가능
await foreach (var x in LibraryStreamGood().ConfigureAwait(false))
{
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;elm&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ConfigureAwait(false) 적용 시
IL_0019: call valuetype ConfiguredTaskAwaitable ...::ConfigureAwait(bool)
IL_001e: call valuetype ConfiguredTaskAwaiter ConfiguredTaskAwaitable::GetAwaiter()
// SynchronizationContext를 캡처하지 않음 &amp;rarr; 스레드 풀에서 그대로 재개&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: Unity는 메인 스레드 컨텍스트가 잡혀 있으므로, 라이브러리에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(false)&lt;/code&gt;를 누락하면 모든 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync&lt;/code&gt;가 메인 스레드로 복귀하느라 프레임 시간이 잠식됩니다. 다만 결과를 UI에 반영하는 &lt;b&gt;소비자 측 코드&lt;/b&gt;에서는 메인 스레드가 필요할 수 있으므로 그쪽은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(true)&lt;/code&gt;(기본값)를 유지하거나, UniTask의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SwitchToMainThread&lt;/code&gt;를 명시적으로 사용합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[C# 버전별 변화] C# 8 도입부터 .NET 9까지&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;C# 8.0 (.NET Core 3.0) &amp;mdash; 최초 도입&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이전에는 비동기로 시퀀스를 흘려보낼 표준 방법이 없었습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Before: C# 7.x &amp;mdash; 콜백 체인 또는 Channel&amp;lt;T&amp;gt;로 수동 구현
public static void Subscribe(Action&amp;lt;int&amp;gt; onNext, Action onComplete)
{
    Task.Run(async () =&amp;gt;
    {
        for (int i = 0; i &amp;lt; 10; i++)
        {
            await Task.Delay(100);
            onNext(i);
        }
        onComplete();
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// After: C# 8 &amp;mdash; 언어 차원 지원
public static async IAsyncEnumerable&amp;lt;int&amp;gt; ProduceAsync()
{
    for (int i = 0; i &amp;lt; 10; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

await foreach (var x in ProduceAsync())
{
    Console.WriteLine(x);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// C# 8 이후 IL에 새로 추가된 형
class System.Collections.Generic.IAsyncEnumerable`1&amp;lt;T&amp;gt;
class System.Collections.Generic.IAsyncEnumerator`1&amp;lt;T&amp;gt;
class System.IAsyncDisposable
class System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute
class System.Runtime.CompilerServices.AsyncIteratorMethodBuilder
class System.Runtime.CompilerServices.EnumeratorCancellationAttribute&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: C# 8.0 = .NET Core 3.0 = .NET Standard 2.1이 패키지로 묶여 같이 출시되었습니다. .NET Standard 2.0 환경에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Microsoft.Bcl.AsyncInterfaces&lt;/code&gt; NuGet 패키지로 인터페이스만 가져다 쓸 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;C# 9.0 (.NET 5) &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetAsyncEnumerator&lt;/code&gt; 패턴 매칭&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 9부터 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;를 명시적으로 구현하지 않아도, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetAsyncEnumerator&lt;/code&gt;라는 &lt;b&gt;확장 메서드&lt;/b&gt;만 있으면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;가 동작합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// C# 9: 확장 메서드로 await foreach 가능
public struct MyStream { /* IAsyncEnumerable 미구현 */ }

public static class MyStreamExtensions
{
    public static IAsyncEnumerator&amp;lt;int&amp;gt; GetAsyncEnumerator(this MyStream s,
        CancellationToken ct = default) =&amp;gt; new MyEnumerator();
}

// 호출자 &amp;mdash; 확장 메서드만으로 동작
await foreach (var x in new MyStream())  // &amp;larr; 인터페이스 구현 없이도 OK
{
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: 외부 라이브러리 타입이나 기존 Unity API를 수정 없이 비동기 스트림처럼 다룰 수 있게 되었습니다. 인터페이스 구현이라는 제약이 풀려 확장성이 커졌습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;.NET 6+ &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Parallel.ForEachAsync&lt;/code&gt; 등장&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;순차 처리만 가능한 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;의 약점을 보완하는 병렬 버전이 추가됐습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// .NET 6 이상: 동시성 제한과 함께 병렬 처리
await Parallel.ForEachAsync(
    ProduceAsync(),                                           // IAsyncEnumerable&amp;lt;T&amp;gt;
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    async (item, ct) =&amp;gt;
    {
        await ProcessAsync(item, ct);
    });&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: 한 건씩 차례로 처리하는 기본 동작 위에, &quot;최대 4개까지 동시에 처리&quot; 같은 제어를 얹을 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;.NET 9 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenEach&lt;/code&gt;와의 결합&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// .NET 9: 여러 Task를 먼저 끝나는 순서대로 await foreach
Task&amp;lt;int&amp;gt;[] tasks = { Task1(), Task2(), Task3() };
await foreach (Task&amp;lt;int&amp;gt; completed in Task.WhenEach(tasks))
{
    var result = await completed; // 먼저 끝난 작업의 결과를 즉시 처리
    Process(result);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해설: 이전에는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAny&lt;/code&gt;를 루프 안에서 호출하며 직접 컬렉션을 갱신해야 했습니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenEach&lt;/code&gt;는 그 패턴을 비동기 스트림으로 깔끔하게 표현합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;[정리] 비동기 스트림 핵심 체크리스트&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] &lt;b&gt;언제 쓰는가&lt;/b&gt; &amp;mdash; 데이터가 비동기로 한 건씩 도착할 때(파일 라인, 페이지네이션, 채팅, 센서). 전부 다 모아서 처리할 거면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;로 충분하다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;시그니처&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async IAsyncEnumerable&amp;lt;T&amp;gt; Method(...)&lt;/code&gt; + 본문에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await ... yield return ...&lt;/code&gt;. 반환 타입을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;IEnumerable&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;로 잘못 쓰지 않는다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;상태머신 구조&lt;/b&gt; &amp;mdash; 컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerator&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncDisposable&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncStateMachine&lt;/code&gt;을 모두 구현하는 중첩 클래스를 생성한다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncIteratorMethodBuilder&lt;/code&gt;와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ManualResetValueTaskSourceCore&amp;lt;bool&amp;gt;&lt;/code&gt;이 핵심 부품이다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;취소 토큰&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;[EnumeratorCancellation] CancellationToken ct = default&lt;/code&gt; 형식으로 받고, 소비자는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WithCancellation(token)&lt;/code&gt;으로 전달. 어트리뷰트가 없으면 토큰이 메서드까지 닿지 않는다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;자원 정리&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;는 컴파일러가 자동으로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-finally&lt;/code&gt;를 감싸 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;DisposeAsync()&lt;/code&gt;를 보장한다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;break&lt;/code&gt;, 예외, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;return&lt;/code&gt; 모두 안전하다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;성능&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;bool&amp;gt;&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ManualResetValueTaskSourceCore&lt;/code&gt; 재사용으로 정상 경로의 힙 할당이 거의 0이다. Unity 모바일 핫패스에서도 안전하게 쓸 수 있다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;라이브러리 코드&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(false)&lt;/code&gt;를 잊지 않는다. Unity 메인 스레드 복귀 비용을 줄여준다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;혼동 금지&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;(병렬 fire) &amp;ne; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;(순차 비동기 pull). 이름만 비슷할 뿐 동작 모델이 정반대다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>'C#'</category>
      <category>'기초'</category>
      <category>'입문'</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/581</guid>
      <comments>https://everyday-devup.tistory.com/581#entry581comment</comments>
      <pubDate>Sat, 9 May 2026 00:31:48 +0900</pubDate>
    </item>
    <item>
      <title>[PART13.비동기와 스레딩 기초(10/15)] Task의 예외 처리</title>
      <link>https://everyday-devup.tistory.com/580</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(10/15)] Task의 예외 처리&lt;/h1&gt;
&lt;p style=&quot;color: #868e96; font-size: 14px; margin: 0 0 32px; line-height: 1.6;&quot; data-ke-size=&quot;size16&quot;&gt;async/await 안에서 던진 예외는 어디로 가는가 / Task.WhenAll의 첫 예외 함정 / AggregateException과 ExceptionDispatchInfo의 진실&lt;/p&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;한 줄 정의&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;시작하기 전에 알아야 할 핵심&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;왜 이 주제가 까다로운가 &amp;mdash; &quot;예외가 사라졌어요&quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;핵심 메커니즘 &amp;mdash; Task에 예외가 저장되고 await에서 부활한다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;내부 동작 &amp;mdash; ExceptionDispatchInfo가 스택 트레이스를 보존하는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;Task.WhenAll의 첫 예외 함정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;async void와 미관찰 예외 &amp;mdash; 사라지는 예외의 정체&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-8&quot;&gt;실전 적용 &amp;mdash; Unity 모바일에서의 패턴&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-9&quot;&gt;함정과 주의사항&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-10&quot;&gt;C# 버전별 변화&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-11&quot;&gt;정리 &amp;mdash; 이것만 기억하세요&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;한 줄 정의&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드 안에서 던진 예외는 즉시 호출자에게 전달되지 않고 반환된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 객체 안에 저장되어 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Faulted&lt;/code&gt; 상태가 됩니다. 호출자가 그 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;하는 순간에야 예외가 다시 살아나 호출자의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt;로 흘러들어 갑니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;시작하기 전에 알아야 할 핵심&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;예외는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;에 저장됩니다&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드 안에서 발생한 예외는 곧바로 위로 전파되지 않고 반환된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;의 상태로 보관됩니다. 그 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;해야 다시 throw됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스택 트레이스는 보존됩니다&lt;/b&gt;: 단순한 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;throw ex;&lt;/code&gt;가 아니라 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ExceptionDispatchInfo&lt;/code&gt;(예외 발송 정보 보관 객체)를 통해 원래 발생 지점의 스택 트레이스가 그대로 살아납니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;은 첫 예외만 던집니다&lt;/b&gt;: 여러 작업이 동시에 실패해도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;로는 첫 번째 예외 하나만 잡힙니다. 나머지를 보려면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;task.Exception&lt;/code&gt;(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt;)을 직접 확인해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;는 예외를 잡을 수 없습니다&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;가 없으니 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;도 못하고, 처리되지 않은 예외는 동기화 컨텍스트로 퍼져 프로세스를 종료시킵니다. 이벤트 핸들러를 제외하면 사용하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관찰되지 않은 예외는 조용히 사라집니다&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;하지도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Result&lt;/code&gt;로 받지도 않으면 그 예외는 GC가 수집할 때 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UnobservedTaskException&lt;/code&gt; 이벤트로만 흘러갑니다. 로깅 후크가 없으면 영원히 묻힙니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;1. 왜 이 주제가 까다로운가 &amp;mdash; &quot;예외가 사라졌어요&quot;&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;동기 코드와 다른 점&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;동기 코드에서 예외는 즉시 호출 스택을 거슬러 올라갑니다. 메서드가 던지면 바로 호출자가 받고, 받지 못하면 그 위로 넘어갑니다. 흐름이 단순합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;는 다릅니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드는 도중에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;를 만나면 호출자에게 미완료 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 하나만 돌려주고 자신은 일단 반환됩니다. 그 뒤에 발생하는 예외는 누구의 호출 스택에도 남아 있지 않습니다. &lt;b&gt;다른 스레드&lt;/b&gt;에서 발생할 수도 있고, 발생 시점에는 호출자가 이미 다른 일을 하고 있을 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 차이가 신입 개발자에게 다음 같은 혼란을 만듭니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Unity에서 흔히 마주치는 상황
public async void OnButtonClicked()
{
    LoadDataAsync(); // ⚠️ await 빼먹음
}

private async Task LoadDataAsync()
{
    await Task.Delay(100);
    throw new InvalidOperationException(&quot;데이터 없음&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드는 어떤 일이 벌어질까요?&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예외가 발생하지만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OnButtonClicked&lt;/code&gt;의 호출 스택에는 도달하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt;로 감싸도 잡히지 않습니다.&lt;/li&gt;
&lt;li&gt;콘솔에는 아무 로그도 남지 않을 수 있습니다(미관찰 예외 정책에 따라).&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 예외 처리의 첫 번째 규칙은 &lt;b&gt;&quot;예외는 Task와 함께 흐른다&quot;&lt;/b&gt;입니다. Task가 끊기면 예외도 끊깁니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;Unity에서 왜 더 무서운가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 모바일 게임에서는 네트워크 호출, 에셋 로드, 어드레서블 다운로드, 인앱 결제 같은 비동기 흐름이 끊임없이 등장합니다. 한 번의 잘못된 예외 처리가 다음 결과로 이어집니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;무음 실패&lt;/b&gt;: 데이터 로드가 실패했는데 로그가 없어 QA가 재현할 수 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태 불일치&lt;/b&gt;: 트랜잭션 중간에서 끊긴 채 UI가 다음 화면으로 넘어가 NRE(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;NullReferenceException&lt;/code&gt;)를 연쇄적으로 발생시킵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;앱 크래시&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;에서 예외가 새어 나가면 모바일 OS가 앱을 강제 종료합니다. 사용자에게는 &quot;튕겼다&quot;는 인상만 남습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재현 불가능한 GC 시점 크래시&lt;/b&gt;: 미관찰 예외가 GC 시점에 보고되어 원래 발생 위치와 무관한 곳에서 알림이 뜹니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 예외를 제대로 다루지 않으면 디버깅의 9할은 추리 게임이 됩니다. 이 글은 그 추리를 줄이는 메커니즘 정리를 목표로 합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;2. 핵심 메커니즘 &amp;mdash; Task에 예외가 저장되고 await에서 부활한다&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;비유로 이해하기&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Task는 &lt;b&gt;운송 컨테이너&lt;/b&gt;입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드 안에서 작업이 진행되다가 예외가 발생하면, 작업자는 그 예외를 컨테이너 안에 넣고 컨테이너에 빨간 라벨(Faulted 상태)을 붙입니다. 컨테이너는 호출자의 창고로 배송됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;호출자가 컨테이너를 그냥 책상에 두면 아무 일도 일어나지 않습니다. 호출자가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;로 컨테이너를 &lt;b&gt;여는 순간&lt;/b&gt;에야 안의 예외가 튀어나와 호출자의 코드에 도달합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/c4wQys/dJMcacpwktl/AAAAAAAAAAAAAAAAAAAAAH09AmYPA_SrlJvTIylLY8iLuKln_nAQQT9didyjCril/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=JVWh3LBfX5C0%2F3vH28XSV9b4j1s%3D&quot; alt=&quot;async 메서드 안에서 던진 예외가 await로 도달하기까지&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;가장 단순한 예시&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        try
        {
            await LoadAsync();
        }
        catch (InvalidOperationException ex)
        {
            // ✅ 여기서 잡힙니다
            Console.WriteLine($&quot;잡았다: {ex.Message}&quot;);
        }
    }

    static async Task LoadAsync()
    {
        await Task.Delay(50);
        throw new InvalidOperationException(&quot;로드 실패&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;LoadAsync&lt;/code&gt;는 자기 안에서 예외를 던졌지만 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Main&lt;/code&gt;의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt;가 잡습니다. 정확히 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await LoadAsync()&lt;/code&gt; 줄에서 다시 throw되기 때문입니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; &amp;mdash; 비동기 메서드 한정자&lt;/b&gt; 메서드를 컴파일러가 상태 머신(State Machine)으로 변환하도록 지시합니다. 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;를 사용할 수 있게 되며, 메서드의 모든 throw&amp;middot;반환은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;로 감싸집니다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task LoadAsync() { await Task.Delay(100); }&lt;/code&gt; 호출자는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;를 받고, 안에서 발생하는 예외도 그 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;에 담겨 흐릅니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;IL로 확인 &amp;mdash; async 메서드는 try-catch로 감싸인다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 컴파일러는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드를 보면 별도의 상태 머신 클래스(struct)를 만들어 작업의 진행 상태를 관리합니다. 그 안의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNext&lt;/code&gt; 메서드는 사실상 거대한 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt;로 둘러싸여 있어, 사용자가 던진 예외를 잡아 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder.SetException&lt;/code&gt;으로 Task에 저장합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;.method private hidebysig instance void MoveNext () cil managed
{
    .locals init (
        [0] int32 V_0,
        [1] class [System.Runtime]System.Exception V_1
    )

    .try
    {
        // 사용자 코드 본문 (await 분기&amp;middot;throw 포함)
        IL_0040: newobj  instance void [System.Runtime]System.InvalidOperationException::.ctor(string)
        IL_0045: throw    // &amp;larr; 사용자가 던진 예외
    }
    catch [System.Runtime]System.Exception
    {
        IL_0050: stloc.1
        IL_0051: ldarg.0
        IL_0052: ldflda  '&amp;lt;&amp;gt;t__builder'
        // 예외를 Task로 흘려보낸다 &amp;mdash; 호출 스택으로 던지지 않는다
        IL_0057: call instance void
                 [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::SetException(class System.Exception)
        IL_005c: leave.s IL_0070
    }
    IL_0070: ret
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 한 줄은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder::SetException&lt;/code&gt;입니다. 사용자가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;throw&lt;/code&gt;로 던진 예외는 호출 스택을 거슬러 올라가는 대신 &lt;b&gt;Task의 상태로 변환&lt;/b&gt;됩니다. 이 변환이 &quot;예외가 Task에 담긴다&quot;의 실체입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;어디까지가 동기 throw인가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드 안에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 등장하기 &lt;b&gt;전까지&lt;/b&gt;의 코드는 동기적으로 실행되지만, 그 부분에서 던진 예외도 똑같이 Task에 담깁니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;static async Task ValidateThenWorkAsync(string? input)
{
    if (input is null)
        throw new ArgumentNullException(nameof(input)); // ❗ 즉시 throw가 아님
    await Task.Delay(50);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;호출자가 보기에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValidateThenWorkAsync(null)&lt;/code&gt;은 즉시 예외를 던지지 않고 &lt;b&gt;이미 Faulted 상태인 Task&lt;/b&gt;를 돌려줍니다. 호출자는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;해야만 알게 됩니다. 이는 인수 검증을 즉시 알리고 싶을 때 의도와 다른 동작이며, 뒤의 [함정] 섹션에서 분리 패턴을 다룹니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;3. 내부 동작 &amp;mdash; ExceptionDispatchInfo가 스택 트레이스를 보존하는 방법&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;단순 throw였다면 어떻게 됐을까&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 작업은 여러 스레드를 넘나들 수 있습니다. 만약 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 단순히 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;throw exception;&lt;/code&gt;을 호출했다면 다음 일이 벌어집니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원래 발생 지점의 스택 트레이스가 사라지고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 라인의 스택 트레이스로 덮어씌워집니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IOException&lt;/code&gt;이 어느 파일 작업에서 발생했는지 알 수 없게 됩니다.&lt;/li&gt;
&lt;li&gt;디버거가 가리키는 위치는 항상 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 라인입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이는 디버깅에 치명적입니다. 그래서 .NET은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;System.Runtime.ExceptionServices.ExceptionDispatchInfo&lt;/code&gt;(예외 발송 정보 보관 객체)라는 우회 장치를 사용합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;두 단계 동작&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/njI5A/dJMcacpwktm/AAAAAAAAAAAAAAAAAAAAAOn9jGd4_AvsQjk6lNOriFs59oAzb4lna4SHllw5tecT/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=thHM%2B%2BqVdWcu3hwtbnTSF8wF5Og%3D&quot; alt=&quot;ExceptionDispatchInfo &amp;mdash; 스택 트레이스를 보존하는 두 단계&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;직접 확인하는 코드&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        try
        {
            await Outer();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }

    static async Task Outer()
    {
        await Task.Delay(10);
        await Inner();
    }

    static async Task Inner()
    {
        await Task.Delay(10);
        throw new InvalidOperationException(&quot;진짜 발생 지점&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;실행하면 스택 트레이스에 다음 두 줄이 모두 등장합니다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot; style=&quot;background: #f1f3f5; color: #495057; padding: 16px 20px; border-radius: 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0 0 16px;&quot;&gt;&lt;code&gt;System.InvalidOperationException: 진짜 발생 지점
   at Program.Inner() in Program.cs:line 26     &amp;larr; 원래 발생 지점이 살아있다
   at Program.Outer() in Program.cs:line 19     &amp;larr; 호출 체인도 보존
   at Program.Main() in Program.cs:line 11&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ExceptionDispatchInfo&lt;/code&gt; 덕분에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Inner&lt;/code&gt; 안의 라인 번호가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 호출 라인에 묻히지 않고 남습니다. C# 5 이전 시절 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Wait()&lt;/code&gt;로 동기 대기하던 방식은 이 보존이 약했고, 그래서 진단이 훨씬 어려웠습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;4. Task.WhenAll의 첫 예외 함정&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;직관과 다른 동작&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;(여러 작업을 모두 기다리는 결합 메서드)을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;하면 &lt;b&gt;하나의 예외만&lt;/b&gt; 던져집니다. 여러 작업이 동시에 실패해도 마찬가지입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var t1 = FailAsync(50, &quot;A 실패&quot;);
        var t2 = FailAsync(60, &quot;B 실패&quot;);
        var t3 = FailAsync(70, &quot;C 실패&quot;);

        try
        {
            await Task.WhenAll(t1, t2, t3);
        }
        catch (Exception ex)
        {
            // ⚠️ 첫 번째 예외 하나만 잡힙니다
            Console.WriteLine($&quot;잡힌 예외: {ex.GetType().Name} &amp;mdash; {ex.Message}&quot;);
        }
    }

    static async Task FailAsync(int delay, string msg)
    {
        await Task.Delay(delay);
        throw new InvalidOperationException(msg);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;출력은 다음 한 줄뿐입니다.&lt;/p&gt;
&lt;pre class=&quot;dns&quot; style=&quot;background: #f1f3f5; color: #495057; padding: 16px 20px; border-radius: 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0 0 16px;&quot;&gt;&lt;code&gt;잡힌 예외: InvalidOperationException &amp;mdash; A 실패&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;B 실패&lt;/code&gt;와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;C 실패&lt;/code&gt;는 어디로 갔을까요? &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;이 반환한 합쳐진 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 안에 살아있습니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 첫 번째만 꺼내 보여주고 나머지를 숨긴 것뿐입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;왜 이렇게 설계되었나&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;언어 설계자의 선택입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt;(여러 예외를 하나로 묶은 컨테이너)을 그대로 던졌다면 사용자는 항상 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;catch (AggregateException)&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;InnerExceptions&lt;/code&gt; 순회 코드를 써야 했을 것입니다. 절대 다수의 코드는 작업이 하나일 때를 다루므로, &quot;단일 예외만 던지는&quot; 단순성을 우선한 결정입니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;대신 모든 예외를 잃어버리지는 않게 했습니다. 결합된 Task의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception&lt;/code&gt; 속성에는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt;이 그대로 들어 있어 직접 꺼낼 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;모든 예외를 보는 두 가지 패턴&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/mOMG5/dJMb990ENVB/AAAAAAAAAAAAAAAAAAAAALZr946gAsIYKEwvEqutzD3FdklT4hCFm2V3xC-0CUnO/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=AbacCnT%2Fu0mVzrAHP2KdwWOMy34%3D&quot; alt=&quot;Task.WhenAll에서 모든 예외 수집하기 &amp;mdash; 두 가지 패턴&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var t1 = FailAsync(50, &quot;A 실패&quot;);
        var t2 = FailAsync(60, &quot;B 실패&quot;);
        var t3 = FailAsync(70, &quot;C 실패&quot;);
        var all = Task.WhenAll(t1, t2, t3);

        try
        {
            await all;
        }
        catch
        {
            // ✅ 모든 예외를 보고 싶으면 합쳐진 Task의 Exception을 직접 꺼낸다
            if (all.Exception is AggregateException ae)
            {
                foreach (var inner in ae.Flatten().InnerExceptions)
                {
                    Console.WriteLine($&quot;- {inner.Message}&quot;);
                }
            }
        }
    }

    static async Task FailAsync(int delay, string msg)
    {
        await Task.Delay(delay);
        throw new InvalidOperationException(msg);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot; style=&quot;background: #f1f3f5; color: #495057; padding: 16px 20px; border-radius: 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0 0 16px;&quot;&gt;&lt;code&gt;- A 실패
- B 실패
- C 실패&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt; &amp;mdash; 여러 예외를 묶는 컨테이너 예외&lt;/b&gt; 병렬 작업처럼 한 번에 여러 예외가 발생할 수 있는 상황을 다루기 위한 타입입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;InnerExceptions&lt;/code&gt; 컬렉션에 모든 예외가 들어 있고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Flatten()&lt;/code&gt; 메서드로 중첩된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt;을 평탄화할 수 있습니다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;foreach (var ex in agg.Flatten().InnerExceptions) Log(ex);&lt;/code&gt; 모든 자식 예외를 한 번에 순회합니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;IL로 보는 await의 예외 처리 코드&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await all;&lt;/code&gt; 한 줄을 IL로 보면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetAwaiter().GetResult()&lt;/code&gt; 호출이 보입니다. 이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetResult&lt;/code&gt;가 내부적으로 첫 예외만 꺼내는 책임자입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// await all; 컴파일 결과 (요약)
IL_0080: ldloca.s   awaiter
IL_0082: call instance !0 valuetype
            [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter::GetResult()
// &amp;uarr; Task.Exception이 AggregateException이라도
//   여기서 InnerExceptions[0]만 ExceptionDispatchInfo로 꺼내 throw&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;TaskAwaiter.GetResult&lt;/code&gt; 내부 구현은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 상태를 점검하다 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Faulted&lt;/code&gt;이면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ExceptionDispatchInfo&lt;/code&gt;로 첫 예외만 발사합니다. 이 동작은 모든 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;에 공통이며, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;만의 특별한 처리가 아니라 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;라는 키워드 자체의 일관된 의미론&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;Task.WhenAny는 다르다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAny&lt;/code&gt;(여러 작업 중 가장 먼저 끝난 하나만 기다리는 메서드)는 &lt;b&gt;자체로는 절대 throw하지 않습니다.&lt;/b&gt; 어떤 작업이 끝났는지를 알려주는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;Task&amp;gt;&lt;/code&gt;를 반환할 뿐입니다. 예외가 발생한 작업이 가장 먼저 끝났다면, 그 작업을 한 번 더 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;해야 예외가 도달합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;var winner = await Task.WhenAny(t1, t2);
await winner; // 예외 처리는 여기서&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 차이는 PART 13-08 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll &amp;middot; WhenAny&lt;/code&gt;에서도 다룬 부분이지만, 예외 흐름 관점에서 다시 짚어둘 가치가 있습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;5. async void와 미관찰 예외 &amp;mdash; 사라지는 예외의 정체&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;async void는 왜 위험한가&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드의 반환형은 셋 중 하나입니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;반환형&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;호출자가 await 가능?&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;예외 전달 경로&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 시점에 throw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 시점에 throw + 결과값 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;동기화 컨텍스트로 직접 발사 &amp;rarr; 처리 안 되면 프로세스 종료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;는 반환할 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;가 없으니 호출자가 결과도 예외도 받을 수 없습니다. 처리되지 않은 예외는 호출 당시의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt;(또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThreadPool&lt;/code&gt;)로 던져지고, 이를 받는 사람이 없으면 .NET 런타임이 처리되지 않은 예외로 간주해 프로세스를 종료시킵니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/sB9VV/dJMcag6v1Vb/AAAAAAAAAAAAAAAAAAAAACV7w4hGllYDqE9NBcaxqg233iyNIL-iG40lV4eL3sdd/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=a3vO%2BIXcmmvNCc5IUjt0Zrwlyv4%3D&quot; alt=&quot;async void vs async Task &amp;mdash; 예외 흐름의 차이&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;실제 예시 &amp;mdash; Unity 버튼 핸들러&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using UnityEngine;
using UnityEngine.UI;
using System;
using System.Threading.Tasks;

public class ShopController : MonoBehaviour
{
    [SerializeField] private Button buyButton;

    void Start() =&amp;gt; buyButton.onClick.AddListener(OnBuyClicked);

    // ❌ Bad &amp;mdash; async void에서 예외가 새면 앱이 죽는다
    private async void OnBuyClicked()
    {
        await PurchaseAsync(); // 네트워크 실패 시 throw
    }

    // ✅ Good &amp;mdash; 이벤트 핸들러 자체는 async void라도, 안에서 try-catch로 감싼다
    private async void OnBuyClickedSafe()
    {
        try
        {
            await PurchaseAsync();
        }
        catch (Exception ex)
        {
            ShowErrorPopup(ex.Message);
            Debug.LogException(ex);
        }
    }

    private async Task PurchaseAsync()
    {
        await Task.Delay(100);
        throw new InvalidOperationException(&quot;결제 서버 응답 없음&quot;);
    }

    private void ShowErrorPopup(string msg) { /* ... */ }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Button.onClick.AddListener&lt;/code&gt;는 시그니처상 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt;만 받으므로 핸들러를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt;로 바꿀 수 없습니다. 이때 핸들러를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;로 두되 &lt;b&gt;본문 전체를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt;로 감싸는 것&lt;/b&gt;이 표준 패턴입니다. 절대 catch에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Debug.LogException&lt;/code&gt;만 넣고 끝내지 말고, 사용자에게도 에러를 알리는 UI를 띄우는 것이 좋습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;미관찰 예외(UnobservedTaskException)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;를 만들고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Result&lt;/code&gt; 호출도 하지 않으면 그 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 안의 예외는 누구의 눈에도 띄지 않습니다. 이 상태에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 객체가 GC에 의해 수집되면 .NET은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;TaskScheduler.UnobservedTaskException&lt;/code&gt; 이벤트를 발생시킵니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.NET Framework 4.0 이전: 이 이벤트가 처리되지 않으면 프로세스를 종료시켰습니다.&lt;/li&gt;
&lt;li&gt;.NET 4.5 이후 (현재): 기본적으로 프로세스를 죽이지 않고 이벤트만 발생시킵니다. 이벤트 핸들러가 없으면 예외는 &lt;b&gt;조용히 사라집니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arcade&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 프로그램 진입점에서 한 번만 등록
TaskScheduler.UnobservedTaskException += (sender, e) =&amp;gt;
{
    Debug.LogError($&quot;[Unobserved] {e.Exception}&quot;);
    e.SetObserved(); // 관찰됐다고 표시 &amp;mdash; 더 이상 위로 전파하지 않음
};

// fire-and-forget 패턴 (보통은 피해야 함)
_ = LongRunningAsync(); // &amp;larr; await 없이 그냥 시작&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 이벤트는 &lt;b&gt;GC 시점에만&lt;/b&gt; 발생합니다. 작업 실패 시점이 아닙니다. 따라서 미관찰 예외를 발견했을 때 이미 한참 시간이 지난 뒤일 수 있고, 발생 위치를 정확히 추적하기 어렵습니다. 의지하기보다는 &lt;b&gt;모든 Task를 누군가 책임지고 await하는 구조&lt;/b&gt;를 만드는 것이 우선입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 8 ===================== --&gt;
&lt;h2 id=&quot;section-8&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;6. 실전 적용 &amp;mdash; Unity 모바일에서의 패턴&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;패턴 1. 입력 검증을 동기 메서드로 분리&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드 본문에 인수 검증을 두면 예외가 즉시 throw되지 않고 Task에 담겨 호출자에게 전달됩니다. 호출자가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 없이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_ =&lt;/code&gt;로 무시하면 검증 실패가 영원히 묵살됩니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ Bad &amp;mdash; async 본문에 검증
public async Task SaveAsync(string? path)
{
    if (path is null)
        throw new ArgumentNullException(nameof(path)); // Task로 감싸짐
    await File.WriteAllTextAsync(path, &quot;data&quot;);
}

// 호출자
_ = controller.SaveAsync(null); // 검증 예외가 묵살됨

// ✅ Good &amp;mdash; 검증은 동기 래퍼, 비동기 본체는 분리
public Task SaveAsync(string? path)
{
    if (path is null)
        throw new ArgumentNullException(nameof(path)); // 즉시 throw
    return SaveCoreAsync(path);
}

private async Task SaveCoreAsync(string path)
{
    await File.WriteAllTextAsync(path, &quot;data&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;호출자가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_ =&lt;/code&gt;로 무시해도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ArgumentNullException&lt;/code&gt;은 동기 throw이므로 즉시 잡혀 디버그 로그에 남습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;패턴 2. 여러 다운로드 작업의 결과 집계&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;게임 시작 시 여러 어드레서블 번들을 동시에 받는 코드입니다. 한두 개가 실패해도 나머지를 살리고 실패 목록을 보고합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public sealed class AssetBootstrap
{
    private readonly List&amp;lt;string&amp;gt; _bundleKeys = new() { &quot;ui&quot;, &quot;stage1&quot;, &quot;stage2&quot;, &quot;audio&quot; };

    public async Task&amp;lt;BootResult&amp;gt; LoadAllAsync()
    {
        var tasks = _bundleKeys
            .Select(key =&amp;gt; (Key: key, Task: LoadBundleAsync(key)))
            .ToList();

        // 모두 끝날 때까지 기다리되, 예외는 따로 모은다
        try
        {
            await Task.WhenAll(tasks.Select(x =&amp;gt; x.Task));
        }
        catch
        {
            // await는 첫 예외만 던지므로, 여기서 catch만 하고 실제 분류는 아래에서
        }

        var failures = tasks
            .Where(x =&amp;gt; x.Task.IsFaulted)
            .Select(x =&amp;gt; (x.Key, Error: x.Task.Exception!.GetBaseException()))
            .ToList();

        return new BootResult(
            Successful: tasks.Where(x =&amp;gt; !x.Task.IsFaulted).Select(x =&amp;gt; x.Key).ToList(),
            Failed: failures);
    }

    private async Task LoadBundleAsync(string key)
    {
        await Task.Delay(50);
        if (key == &quot;stage2&quot;)
            throw new InvalidOperationException(&quot;stage2 번들 손상&quot;);
    }
}

public record BootResult(
    List&amp;lt;string&amp;gt; Successful,
    List&amp;lt;(string Key, Exception Error)&amp;gt; Failed);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;개별 task 객체를 보존&lt;/b&gt;하는 것입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;에 흘려보내고 끝내지 않고 컬렉션에 두면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsFaulted&lt;/code&gt; 검사로 어느 작업이 어떤 이유로 실패했는지 정확히 알 수 있습니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;의 첫 예외 의미론을 우회하는 가장 깔끔한 방법입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;패턴 3. ConfigureAwait(false)와 라이브러리 코드&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드는 기본적으로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 직전의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt;(예: Unity 메인 스레드)에서 이어 실행되도록 컨텍스트를 캡처합니다. 라이브러리 내부에서까지 캡처하면 메인 스레드 데드락의 원인이 됩니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 게임 라이브러리 내부 코드
public async Task&amp;lt;byte[]&amp;gt; DownloadAsync(string url)
{
    using var http = new HttpClient();
    // ✅ 라이브러리 코드는 컨텍스트 복귀가 불필요
    var bytes = await http.GetByteArrayAsync(url).ConfigureAwait(false);
    return bytes;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(bool)&lt;/code&gt; &amp;mdash; 컨텍스트 복귀 여부 설정&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 후 원래 동기화 컨텍스트로 돌아갈지 결정합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;false&lt;/code&gt;면 ThreadPool에서 이어 실행합니다. UI 라이브러리 내부, 네트워크 호출, 파일 I/O 같은 곳에 사용합니다. UI를 직접 갱신해야 하는 코드(예: Unity의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;transform.position&lt;/code&gt; 갱신)에서는 사용하지 않습니다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await db.QueryAsync().ConfigureAwait(false);&lt;/code&gt; 응답 파싱은 ThreadPool에서 처리되어 UI 스레드를 차지하지 않습니다.&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(false)&lt;/code&gt;는 예외 처리와 직접 관련은 없지만, &lt;b&gt;데드락으로 인한 가짜 예외&lt;/b&gt;(타임아웃, OperationCanceledException 등)를 줄여주므로 묶어서 기억해두면 좋습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;패턴 4. 취소와 예외의 구분&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;이 발사되었음을 알리는 표준 예외)은 일반 오류와 구분해서 처리해야 합니다. PART 13-09에서 다룬 취소 토큰의 동반자입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;try
{
    await DownloadAsync(url, token);
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
    // 정상적인 취소 &amp;mdash; 사용자에게 에러 토스트를 띄우지 않는다
    Debug.Log(&quot;사용자가 다운로드를 취소했습니다.&quot;);
}
catch (Exception ex)
{
    // 진짜 오류
    ShowErrorPopup(ex);
    Debug.LogException(ex);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;when&lt;/code&gt; &amp;mdash; catch 필터 (Exception filter)&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;catch&lt;/code&gt; 절에 추가 조건을 붙여서 조건이 참일 때만 그 catch가 활성화되도록 합니다. 거짓이면 마치 그 catch가 없는 것처럼 다음 catch를 찾으러 갑니다. 스택을 풀지 않은 채 검사하므로 진단에 유리합니다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;catch (HttpException ex) when (ex.StatusCode == 503)&lt;/code&gt; 503일 때만 잡고, 그 외 HttpException은 다른 catch로 넘어갑니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;패턴 5. ValueTask의 예외 처리 차이&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt;(할당을 줄이기 위한 구조체 기반 Task 대체재)는 GC 압박이 큰 핫패스에서 사용됩니다. 예외 처리 자체는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;와 동일하지만 두 가지 주의점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;두 번 await 금지&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt;는 한 번만 await할 수 있습니다. 두 번 하면 예외가 아닌 정의되지 않은 동작이 발생합니다. 두 번 쓸 일이 있다면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.AsTask()&lt;/code&gt;로 변환해 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;로 보관합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;에 못 넣음&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IEnumerable&amp;lt;Task&amp;gt;&lt;/code&gt;를 받습니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt;는 일단 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.AsTask()&lt;/code&gt;로 변환해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public ValueTask&amp;lt;int&amp;gt; GetCachedOrLoadAsync(string key)
{
    if (_cache.TryGetValue(key, out var cached))
        return new ValueTask&amp;lt;int&amp;gt;(cached); // 즉시 반환 &amp;mdash; 할당 없음
    return new ValueTask&amp;lt;int&amp;gt;(LoadAsync(key));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 모바일에서 매 프레임 호출되는 코드라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt; 도입을 검토할 가치가 있습니다. 하지만 예외 처리 패턴은 그대로이므로 추가로 익힐 것은 없습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 9 ===================== --&gt;
&lt;h2 id=&quot;section-9&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;7. 함정과 주의사항&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정 1. catch (AggregateException)으로 잡으려 시도&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Wait()&lt;/code&gt; 시절의 패턴을 그대로 가져오는 실수입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ Bad &amp;mdash; await는 AggregateException을 풀어서 던진다
try
{
    await Task.WhenAll(t1, t2, t3);
}
catch (AggregateException ae) // 절대 잡히지 않음
{
    foreach (var inner in ae.InnerExceptions) Log(inner);
}

// ✅ Good &amp;mdash; 예외 타입 자체로 잡고, 모든 예외를 보려면 Task.Exception 사용
try
{
    var all = Task.WhenAll(t1, t2, t3);
    await all;
}
catch (Exception)
{
    if (all.Exception is AggregateException ae)
        foreach (var inner in ae.Flatten().InnerExceptions) Log(inner);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException.InnerExceptions[0]&lt;/code&gt;만 풀어 던집니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;catch (AggregateException)&lt;/code&gt; 절은 영원히 활성화되지 않습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정 2. fire-and-forget을 _ =로 가린다&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ Bad &amp;mdash; 예외가 미관찰로 묻힘
_ = SaveLogAsync(payload);

// ✅ Good &amp;mdash; 예외 처리를 명시한 fire-and-forget 헬퍼
public static class TaskExt
{
    public static void Forget(this Task task, string context)
    {
        task.ContinueWith(t =&amp;gt;
            {
                if (t.IsFaulted)
                    Debug.LogError($&quot;[{context}] {t.Exception?.Flatten()}&quot;);
            },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

// 호출
SaveLogAsync(payload).Forget(nameof(SaveLogAsync));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask&lt;/code&gt;라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Forget()&lt;/code&gt;이 내장되어 있어 동일한 안전성을 제공합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;만 쓰는 환경에서는 위 같은 헬퍼를 한 번 정의해두고 fire-and-forget 호출에 일관되게 적용합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정 3. try-catch가 await보다 위에 있어 예외를 못 잡는다&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ Bad &amp;mdash; try가 너무 일찍 끝난다
public async Task LoadAsync()
{
    Task download;
    try
    {
        download = StartDownloadAsync(); // 시작만 함
    }
    catch (Exception ex)
    {
        Log(ex);
        return;
    }
    await download; // &amp;larr; 진짜 예외는 여기서 발생
}

// ✅ Good &amp;mdash; await를 try 안에 둔다
public async Task LoadAsync()
{
    try
    {
        var download = StartDownloadAsync();
        await download;
    }
    catch (Exception ex)
    {
        Log(ex);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드를 호출만 하면 동기 부분에서 던진 예외만 잡힙니다. 실제 작업 실패는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 시점에 도달하므로 try-catch는 반드시 await를 둘러싸야 합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정 4. Task.Result나 Task.Wait()로 동기 대기&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 함정은 데드락 문제이기도 하지만, 예외 처리 관점에서도 위험합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ Bad &amp;mdash; Wait/Result는 AggregateException을 그대로 던진다
try
{
    var result = LoadAsync().Result; // &amp;larr; 데드락 위험 + AggregateException
}
catch (InvalidOperationException) // 잡히지 않음
{
    // ...
}

// ✅ Good &amp;mdash; 항상 await 사용
try
{
    var result = await LoadAsync();
}
catch (InvalidOperationException)
{
    // 잡힌다
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 메인 스레드에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt;를 쓰면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt; 캡처와 결합해 영구 데드락이 발생합니다. 게다가 예외 타입 매칭도 깨집니다. 동기 대기는 시작 코드(예: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Main&lt;/code&gt; 진입)나 콘솔 도구에서만 허용합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정 5. UniTask와 Task 혼용 시 예외 변환&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;UniTask 환경에서도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;를 받는 외부 라이브러리를 호출할 일이 생깁니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask&lt;/code&gt;로 변환할 때 예외도 함께 따라옵니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Task &amp;rarr; UniTask 변환
UniTask uni = someTask.AsUniTask(); // 예외도 함께 전달됨
await uni; // Task의 예외 그대로 throw&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;문제는 변환 과정에서 추가 컨텍스트 캡처가 발생하지 않는다는 점입니다. UniTask는 PlayerLoop 기반이고 Task는 ThreadPool 기반이라 catch 시점의 스레드가 다를 수 있습니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Debug.Log&lt;/code&gt;처럼 메인 스레드를 요구하는 호출이 catch 안에 있다면 문제가 생길 수 있습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 10 ===================== --&gt;
&lt;h2 id=&quot;section-10&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;8. C# 버전별 변화&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 예외 처리의 핵심은 C# 5에서 거의 결정되었고 이후는 작은 개선입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;C# 5 (.NET 4.5, 2012) &amp;mdash; async/await 도입과 ExceptionDispatchInfo 등장&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 키워드와 상태 머신 기반 컴파일이 도입되었습니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;는 처음부터 단일 예외만 던지는 의미론이었습니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ExceptionDispatchInfo&lt;/code&gt;가 함께 추가되어 비동기 예외의 스택 트레이스 보존이 가능해졌습니다.&lt;/li&gt;
&lt;li&gt;기본 미관찰 예외 정책이 &quot;프로세스 종료&quot;에서 &quot;이벤트만 발생&quot;으로 변경되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;C# 6 (.NET 4.6, 2015) &amp;mdash; catch/finally에서 await 허용&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 5까지는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;catch&lt;/code&gt;나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally&lt;/code&gt; 블록 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;를 사용할 수 없어, 예외 처리 중 비동기 정리(예: 임시 파일 비동기 삭제)가 불가능했습니다. C# 6부터 가능해졌습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;aspectj&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ C# 5에서는 컴파일 에러
try
{
    await DownloadAsync();
}
catch (Exception ex)
{
    // C# 6부터 OK
    await LogToServerAsync(ex);
    throw;
}
finally
{
    // C# 6부터 OK
    await CleanupTempFilesAsync();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 변화는 IL 수준에서 상태 머신이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;catch&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally&lt;/code&gt; 구조를 가로지를 수 있도록 컴파일러가 확장된 결과입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;C# 6 &amp;mdash; 예외 필터(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;when&lt;/code&gt;)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;같은 시기에 도입된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;when&lt;/code&gt; 키워드는 비동기 예외 처리와 궁합이 좋습니다. 스택 풀기 없이 조건을 검사하므로 진단 정보가 보존됩니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 취소 예외만 분리해 잡되, 토큰이 실제로 발사된 경우만
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
    // 정상 종료
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;C# 7+ (.NET Core 2.0~) &amp;mdash; ValueTask, async Main, ValueTask&amp;lt;T&amp;gt;&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C# 7.1: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task Main&lt;/code&gt;이 가능해져 콘솔 앱 진입점에서 await을 사용할 수 있게 되었습니다.&lt;/li&gt;
&lt;li&gt;C# 7+: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt;가 도입되었지만, 예외 처리 의미론은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;와 동일합니다(앞 [실전 적용] 패턴 5 참조).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;C# 8 (.NET Core 3.0, 2019) &amp;mdash; 비동기 스트림(IAsyncEnumerable)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;도 동일한 예외 의미론을 따릅니다. 스트림 안에서 던진 예외는 다음 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNextAsync&lt;/code&gt;의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 시점에 부활합니다. 이는 PART 13-11에서 다룹니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;C# 11+ &amp;mdash; 변화 없음 (의미론 안정)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 예외 처리 의미론은 C# 11 이후 새로운 변화가 없습니다. 언어 차원에서는 이미 안정화 단계입니다. 라이브러리 차원의 개선(예: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Parallel.ForEachAsync&lt;/code&gt;의 예외 처리 옵션)은 계속되고 있지만 본 글의 범위를 벗어납니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 11 ===================== --&gt;
&lt;h2 id=&quot;section-11&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;9. 정리 &amp;mdash; 이것만 기억하세요&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;예외는 Task와 함께 흐른다&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드의 예외는 즉시 throw되지 않고 Task의 상태(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Faulted&lt;/code&gt;)와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception&lt;/code&gt; 속성으로 보관됩니다. 호출자가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;해야 다시 살아납니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스택 트레이스는 보존된다&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ExceptionDispatchInfo&lt;/code&gt; 덕분에 원래 발생 지점이 살아남습니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 라인이 아닌 진짜 발생 위치로 디버그할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;은 첫 예외만 던진다&lt;/b&gt;: 모든 예외를 보려면 합쳐진 Task의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception&lt;/code&gt; 속성(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt;)을 직접 확인하거나, 개별 task 컬렉션을 보존해 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsFaulted&lt;/code&gt; 검사로 분류합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;는 이벤트 핸들러에만 사용한다&lt;/b&gt;: 그 외에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt;를 사용합니다. 이벤트 핸들러로 쓸 때도 본문 전체를 try-catch로 감쌉니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모든 Task는 누군가 await해야 한다&lt;/b&gt;: fire-and-forget이 필요하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.ContinueWith&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask.Forget&lt;/code&gt;으로 예외를 명시적으로 처리합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_ =&lt;/code&gt;로 가리지 마세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;은 오류가 아니다&lt;/b&gt;: 일반 예외와 분리해서 처리합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;when (token.IsCancellationRequested)&lt;/code&gt; 필터로 정확히 잡습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Result&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Wait()&lt;/code&gt;는 쓰지 않는다&lt;/b&gt;: 데드락과 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt; 매칭 깨짐을 동시에 가져옵니다. 항상 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 예외 처리의 본질은 &quot;예외가 동기 코드와 다른 시간선을 흐른다&quot;는 사실을 받아들이는 것입니다. Task가 끊기지 않도록 호출 체인을 이어 두면, 예외도 자연스럽게 호출자까지 도달합니다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>C#</category>
      <category>기초</category>
      <category>입문</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/580</guid>
      <comments>https://everyday-devup.tistory.com/580#entry580comment</comments>
      <pubDate>Sat, 9 May 2026 00:21:38 +0900</pubDate>
    </item>
    <item>
      <title>[PART13.비동기와 스레딩 기초(9/15)] 취소 &amp;mdash; CancellationToken &amp;middot; CancellationTokenSource</title>
      <link>https://everyday-devup.tistory.com/579</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(9/15)] 취소 &amp;mdash; CancellationToken &amp;middot; CancellationTokenSource&lt;/h1&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;한 줄 정의&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;시작하기 전에 알아야 할 핵심&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;왜 협조적 취소인가 &amp;mdash; `Thread.Abort`가 폐기된 이유&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;두 객체의 역할 &amp;mdash; 발신기와 수신기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;ADEPT &amp;mdash; 토큰을 직접 만지며 이해하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;IsCancellationRequested vs ThrowIfCancellationRequested&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;IL 관점 &amp;mdash; `ThrowIfCancellationRequested`는 얼마나 가벼운가&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-8&quot;&gt;CancelAfter &amp;mdash; 타임아웃의 표준 패턴&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-9&quot;&gt;OperationCanceledException 처리 &amp;mdash; 오류가 아닙니다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-10&quot;&gt;토큰 전파 &amp;mdash; 끝까지 넘기지 않으면 의미가 없다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-11&quot;&gt;Register 콜백 &amp;mdash; 토큰을 모르는 코드에 끼우기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-12&quot;&gt;Unity 실전 활용&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-13&quot;&gt;일반적인 함정 정리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-14&quot;&gt;빠른 점검 체크리스트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-15&quot;&gt;한 걸음 더&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-16&quot;&gt;마무리&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;한 줄 정의&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;은 비동기&amp;middot;장기 실행 작업에 &quot;이제 그만&quot;이라는 신호를 전달하는 협조적 취소(cooperative cancellation) 토큰이며, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenSource&lt;/code&gt;는 그 신호를 발사하는 발신기입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;시작하기 전에 알아야 할 핵심&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;취소는 강제 종료가 아닙니다&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Thread.Abort()&lt;/code&gt;처럼 외부에서 스레드를 죽이는 게 아니라, 작업 코드가 스스로 &quot;취소됐는지 확인하고 정리한 뒤 종료&quot;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;두 객체는 한 쌍입니다&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenSource&lt;/code&gt;(발신기)가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;(수신기)을 만들어 작업에 전달합니다. 발신기는 호출 측이, 수신기는 작업 측이 갖습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확인 패턴은 두 가지&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCancellationRequested&lt;/code&gt;(bool 폴링)와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested()&lt;/code&gt;(예외 발생). &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;에서는 후자가 표준입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;취소 예외는 오류가 아닙니다&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;은 &quot;정상적인 취소&quot;를 의미하므로 일반 예외 로깅과 분리해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토큰은 끝까지 전파해야 합니다&lt;/b&gt;: 호출 체인의 어느 한 곳에서 토큰을 빼먹으면 그 아래는 취소되지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;1. 왜 협조적 취소인가 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Thread.Abort&lt;/code&gt;가 폐기된 이유&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;강제 종료의 위험성&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 1.0 시절에는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Thread.Abort()&lt;/code&gt;로 다른 스레드를 강제 종료할 수 있었습니다. 그러나 .NET Core부터 이 메서드는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;PlatformNotSupportedException&lt;/code&gt;을 던지며 사실상 폐기됐습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이유는 명확합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;공유 상태 불일치&lt;/b&gt;: 트랜잭션 중간에 강제 종료되면 자료구조가 절반만 갱신된 상태로 남습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리소스 누수&lt;/b&gt;: 파일 핸들&amp;middot;소켓&amp;middot;잠금이 해제되지 않은 채 스레드가 사라집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정적 생성자 파괴&lt;/b&gt;: 클래스 초기화 도중 종료되면 해당 타입 전체가 사용 불가능해집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비관리 코드 중단 불가&lt;/b&gt;: 네이티브 호출 중에는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThreadAbortException&lt;/code&gt;이 즉시 발생하지 않아 결과를 예측할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;요컨대 강제 종료는 &quot;프로세스가 일관성을 잃는다&quot;는 본질적 결함을 가집니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;협조적 취소의 합의&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;협조적 취소는 다음 두 가지 약속에 기반합니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;호출 측은 신호만 보낸다&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;cts.Cancel()&lt;/code&gt;은 플래그를 켤 뿐 어떤 스레드도 죽이지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작업 측은 안전한 시점에 스스로 종료한다&lt;/b&gt;: 루프 사이, I/O 사이, 작업 단위 끝에서 토큰을 확인하고 정리한 뒤 빠져나옵니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 모델은 데이터 일관성을 작업 코드의 책임으로 돌리지만, 그 대가로 어떤 강제 종료보다도 안전합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/sk4vT/dJMcagk7ZwO/AAAAAAAAAAAAAAAAAAAAAFdpsy8x9NfU8pBY6WepoQ9U2UgV3BIQrhpZFrOBmT1K/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=9xvLxhtFp%2F6AfC7t5GUNZomzLRk%3D&quot; alt=&quot;Thread.Abort (강제) vs 협조적 취소 (CancellationToken)&quot; /&gt;&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;2. 두 객체의 역할 &amp;mdash; 발신기와 수신기&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;CancellationTokenSource (CTS)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;신호를 발생시키는 쪽입니다. 다음 책임을 가집니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;취소 시작&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Cancel()&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancelAfter(TimeSpan)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토큰 발급&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Token&lt;/code&gt; 속성으로 작업에 전달할 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;을 만듦&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리소스 정리&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IDisposable&lt;/code&gt;이므로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose()&lt;/code&gt;로 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;CancellationToken (CT)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;신호를 수신하는 쪽입니다. 가벼운 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;struct&lt;/code&gt;로 다음을 제공합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상태 조회&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCancellationRequested&lt;/code&gt; (bool)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예외 트리거&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;콜백 등록&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Register(Action)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;취소 핸들&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitHandle&lt;/code&gt; (블로킹 대기용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;토큰은 &lt;b&gt;스스로 취소를 시작할 수 없습니다.&lt;/b&gt; 오직 자신을 만든 CTS의 신호를 감지할 뿐입니다. 이 비대칭이 보안과 책임을 분리합니다 &amp;mdash; 작업 측은 토큰만 받았으므로 외부 취소를 강제할 수 없고, 호출 측만 CTS를 가지므로 취소 시점을 통제합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;     // 작업에 전달할 수신기

var task = DoWorkAsync(token);            // 작업 시작

await Task.Delay(1000);
cts.Cancel();                             // 외부에서 취소 신호

try { await task; }
catch (OperationCanceledException) { Console.WriteLine(&quot;취소됨&quot;); }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;3. ADEPT &amp;mdash; 토큰을 직접 만지며 이해하기&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;A. Analogy (비유)&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenSource&lt;/code&gt;는 &lt;b&gt;공사장 비상 정지 버튼&lt;/b&gt;이고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;은 &lt;b&gt;버튼 신호를 받는 작업자의 이어폰&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;감독(호출자)은 비상 정지 버튼(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Cancel()&lt;/code&gt;)을 누릅니다.&lt;/li&gt;
&lt;li&gt;작업자(작업 코드)는 이어폰(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;token&lt;/code&gt;)으로 &quot;정지&quot; 신호를 듣습니다.&lt;/li&gt;
&lt;li&gt;신호를 들은 작업자는 &lt;b&gt;들고 있는 도구를 안전하게 내려놓고&lt;/b&gt;(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally { 자원 해제 }&lt;/code&gt;) 작업장을 나옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 &quot;감독이 작업자의 손을 잡아채는 게 아니다&quot;라는 점입니다. 감독은 신호만 보내고, 멈출 책임은 작업자에게 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;D. Diagram (구조도)&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/cmLbzn/dJMcaf0Onr1/AAAAAAAAAAAAAAAAAAAAAFaRqcBAdZpyoBW5uTUrIctVbUBrLHIHnM6KyYF8KbEt/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=yxR1A7V0IgTdScgvDJc3Xx3zUMI%3D&quot; alt=&quot;CancellationTokenSource &amp;harr; CancellationToken 신호 흐름&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;E. Example (가장 단순한 예시)&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using var cts = new CancellationTokenSource();
        cts.CancelAfter(TimeSpan.FromSeconds(2));   // 2초 후 자동 취소

        try
        {
            await CountAsync(cts.Token);
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine(&quot;취소됨 (정상)&quot;);
        }
    }

    static async Task CountAsync(CancellationToken token)
    {
        for (int i = 0; i &amp;lt; 100; i++)
        {
            token.ThrowIfCancellationRequested();   // 취소 시 OperationCanceledException
            Console.WriteLine($&quot;카운트 {i}&quot;);
            await Task.Delay(500, token);            // Delay도 토큰을 받음
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;실행하면 약 2초 동안 카운트가 출력되다가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;취소됨 (정상)&lt;/code&gt;으로 끝납니다. 이 8줄이 협조적 취소의 본질을 모두 담고 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;P. Pattern (반복되는 패턴)&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;상황&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;권장 방식&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드 내부&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested()&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;TPL이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Canceled&lt;/code&gt; 상태로 인식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;CPU 집약 루프&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;if (token.IsCancellationRequested) break;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;정리 후 자연 종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;BCL 비동기 호출&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await stream.ReadAsync(buf, token)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;토큰 그대로 전파&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;타임아웃&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;cts.CancelAfter(...)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;다중 조건&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CreateLinkedTokenSource(t1, t2)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;OR 결합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;콜백&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;token.Register(() =&amp;gt; socket.Close())&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt; 잊지 말 것&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;T. Tendency (실패하기 쉬운 곳)&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;을 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception&lt;/code&gt;으로 잡아 로깅하면 진짜 오류와 섞입니다.&lt;/li&gt;
&lt;li&gt;토큰을 받았는데 하위 호출에 전달하지 않으면 그 아래는 취소되지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run(() =&amp;gt; DoWork(token))&lt;/code&gt;처럼 토큰을 람다 캡처만 하고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt; 자체에는 전달하지 않으면, 시작 전 취소가 무시됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenSource&lt;/code&gt;를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;하지 않으면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Register&lt;/code&gt;된 콜백&amp;middot;타이머가 남아 메모리가 누적됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;4. IsCancellationRequested vs ThrowIfCancellationRequested&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;두 방식의 차이는 단순한 구문 차이가 아니라 &lt;b&gt;TPL이 작업 상태를 어떻게 분류하느냐&lt;/b&gt;의 차이입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;IsCancellationRequested &amp;mdash; 폴링&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task ProcessAsync(CancellationToken token)
{
    while (HasMore())
    {
        if (token.IsCancellationRequested)
        {
            CleanupBuffers();    // 정리
            return;              // 자연 종료 &amp;rarr; TaskStatus.RanToCompletion
        }
        await DoStepAsync();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;return&lt;/code&gt;으로 빠져나오면 TPL은 작업이 &lt;b&gt;성공적으로 완료&lt;/b&gt;된 것으로 봅니다(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;TaskStatus.RanToCompletion&lt;/code&gt;). 호출자는 &quot;취소된 건지 일을 다 마친 건지&quot; 구분할 수 없습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;ThrowIfCancellationRequested &amp;mdash; 예외&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task ProcessAsync(CancellationToken token)
{
    while (HasMore())
    {
        token.ThrowIfCancellationRequested();   // &amp;rarr; TaskStatus.Canceled
        await DoStepAsync();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;예외가 발생하면 TPL은 작업 상태를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;TaskStatus.Canceled&lt;/code&gt;로 전환합니다. 이 상태는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 호출자에게 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;으로 다시 전달되며, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.IsCanceled&lt;/code&gt;로 확인할 수도 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;어느 쪽을 써야 하는가&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드는 거의 항상 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested&lt;/code&gt;&lt;/b&gt; &amp;mdash; 호출자가 취소를 명확히 알아야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally&lt;/code&gt;나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/catch&lt;/code&gt; 정리 로직이 길면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCancellationRequested&lt;/code&gt;&lt;/b&gt; &amp;mdash; 정리 후 자연 종료하는 편이 깔끔합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;둘을 섞어도 됩니다&lt;/b&gt; &amp;mdash; 무거운 정리는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCancellationRequested&lt;/code&gt;로 분기, 끝부분에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested&lt;/code&gt;로 상태를 명확히 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task ProcessAsync(CancellationToken token)
{
    try
    {
        while (HasMore())
        {
            if (token.IsCancellationRequested)
            {
                await FlushPartialAsync();   // 부분 결과 저장 등 정리
                token.ThrowIfCancellationRequested();   // 상태 명확히
            }
            await DoStepAsync();
        }
    }
    finally { Cleanup(); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;5. IL 관점 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested&lt;/code&gt;는 얼마나 가벼운가&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 코드:&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Threading;

public class Worker
{
    public void Check(CancellationToken token)
    {
        token.ThrowIfCancellationRequested();
    }

    public bool Poll(CancellationToken token)
    {
        return token.IsCancellationRequested;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Check&lt;/code&gt; 메서드의 IL 골자(개념적):&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;.method public hidebysig instance void Check(valuetype CancellationToken token) cil managed
{
    ldarga.s  token
    call      instance void [System.Threading]CancellationToken::ThrowIfCancellationRequested()
    ret
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;struct&lt;/code&gt;이므로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ldarga.s&lt;/code&gt;로 토큰의 주소를 로드한 뒤 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;call&lt;/code&gt; 명령으로 메서드를 호출합니다. 박싱이 발생하지 않습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested&lt;/code&gt; 내부 동작 (개념적 디컴파일)&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 실제 .NET 런타임 구현 요지
public void ThrowIfCancellationRequested()
{
    if (IsCancellationRequested)
        ThrowOperationCanceledException();   // 분리된 cold path
}

public bool IsCancellationRequested
{
    get
    {
        CancellationTokenSource source = _source;
        return source != null &amp;amp;&amp;amp; source.IsCancellationRequested;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;취소되지 않은 일반 경로(hot path)는 두 번의 필드 읽기와 한 번의 비교만 한다&lt;/b&gt;는 점입니다. 예외 발생 코드는 별도 메서드(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowOperationCanceledException&lt;/code&gt;)로 분리되어 있어 인라이닝되지 않으며, JIT는 이 분기를 cold path로 최적화합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;성능 측정 &amp;mdash; 폴링 비용&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;[MemoryDiagnoser]
public class CancellationBenchmark
{
    private CancellationToken _token = new CancellationTokenSource().Token;

    [Benchmark] public bool Poll() =&amp;gt; _token.IsCancellationRequested;
    [Benchmark] public void Throw() =&amp;gt; _token.ThrowIfCancellationRequested();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;대표적 결과(개발자 노트북, 참고용):&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;메서드&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;시간&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;할당&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCancellationRequested&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;~0.5 ns&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;0 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested&lt;/code&gt; (취소 안 된 경우)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;~0.6 ns&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;0 B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론&lt;/b&gt;: 루프 안에서 매 반복 호출해도 성능에 거의 영향이 없습니다. 따라서 &quot;성능 때문에 안 쓴다&quot;는 잘못된 절약 시도입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;단, 예외가 실제로 던져지면 비용이 큽니다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;이 발생하는 경우는 일반 예외와 동일하게 스택 워킹&amp;middot;예외 객체 할당이 발생하므로 마이크로초 단위입니다. 그러나 이는 &lt;b&gt;작업 종료 경로&lt;/b&gt;이므로 전체 작업 시간 대비 무시 가능합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 8 ===================== --&gt;
&lt;h2 id=&quot;section-8&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;6. CancelAfter &amp;mdash; 타임아웃의 표준 패턴&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;기본 사용&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
// 또는: cts.CancelAfter(5000);

try
{
    var data = await httpClient.GetStringAsync(url, cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
    Console.WriteLine(&quot;5초 타임아웃&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;호출자 토큰과 타임아웃 결합 &amp;mdash; Linked Token&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;호출자가 이미 토큰을 줬는데, 그 위에 타임아웃을 더하고 싶다면 Linked Token을 만듭니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async Task&amp;lt;string&amp;gt; FetchAsync(string url, CancellationToken caller)
{
    using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
    using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
        caller, timeoutCts.Token);

    try
    {
        return await _http.GetStringAsync(url, linkedCts.Token);
    }
    catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested
                                             &amp;amp;&amp;amp; !caller.IsCancellationRequested)
    {
        throw new TimeoutException(&quot;5초 타임아웃&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 패턴은 &quot;호출자 취소&quot;와 &quot;타임아웃 취소&quot;를 구분합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;when&lt;/code&gt; 필터로 어느 쪽이 발사됐는지 확인해 적절한 예외로 변환합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/xTCtO/dJMcaf0Onr2/AAAAAAAAAAAAAAAAAAAAANp6x2VxCvUGaQT60zEcv8lM-JcI-E79U762u3_i5GpB/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Q81uXLthIGED%2B5lnAgNP2MAzBt8%3D&quot; alt=&quot;Linked Token Source &amp;mdash; OR 결합&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;함정: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 빠뜨림&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 잘못된 예
public async Task&amp;lt;string&amp;gt; FetchAsync(string url, CancellationToken caller)
{
    var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));   // using 없음
    var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(caller, timeoutCts.Token);
    return await _http.GetStringAsync(url, linkedCts.Token);
    // timeoutCts&amp;middot;linkedCts가 GC될 때까지 타이머&amp;middot;콜백이 살아있음
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancelAfter&lt;/code&gt; 내부에는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Timer&lt;/code&gt;가 등록되며, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CreateLinkedTokenSource&lt;/code&gt;는 부모 토큰에 콜백을 등록합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;하지 않으면 작업 완료 후에도 자원이 남아 누적됩니다. 짧은 작업에선 티가 안 나지만 분당 수백 건을 처리하는 서비스에선 누수가 됩니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 9 ===================== --&gt;
&lt;h2 id=&quot;section-9&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;7. OperationCanceledException 처리 &amp;mdash; 오류가 아닙니다&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;잘못된 예 (가장 흔한 함정)&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;try
{
    await ProcessAsync(token);
}
catch (Exception ex)
{
    _logger.LogError(ex, &quot;처리 실패&quot;);   // 취소도 &quot;실패&quot;로 기록됨
    ShowErrorDialog(ex.Message);          // 사용자에게 &quot;오류 발생!&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 사용자가 &quot;취소&quot; 버튼을 눌렀는데 &quot;오류!&quot; 팝업이 뜹니다. 로그도 진짜 오류와 정상 취소가 섞여 디버깅이 어려워집니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;올바른 예&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;try
{
    await ProcessAsync(token);
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
    // 정상 취소 &amp;mdash; 로그 안 남김 또는 Info 레벨로만
    _logger.LogInformation(&quot;사용자 요청으로 작업 취소&quot;);
}
catch (Exception ex)
{
    _logger.LogError(ex, &quot;처리 실패&quot;);
    ShowErrorDialog(ex.Message);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;when&lt;/code&gt; 필터로 &quot;내가 요청한 토큰이 취소된 경우&quot;인지 확인하는 게 중요합니다. 이렇게 해야 다른 이유로 발생한 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;(예: 라이브러리 내부 타임아웃)을 일반 예외로 처리할 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WaitAll&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Parallel.For&lt;/code&gt;에서는 AggregateException&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;try
{
    Task.WaitAll(t1, t2, t3);
}
catch (AggregateException agg)
{
    foreach (var inner in agg.InnerExceptions)
    {
        if (inner is OperationCanceledException) continue;   // 취소는 무시
        _logger.LogError(inner, &quot;병렬 작업 실패&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException.Handle(predicate)&lt;/code&gt;도 같은 용도로 쓰입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 10 ===================== --&gt;
&lt;h2 id=&quot;section-10&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;8. 토큰 전파 &amp;mdash; 끝까지 넘기지 않으면 의미가 없다&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;안티패턴: 중간에서 토큰을 잃어버리기&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async Task SaveUserAsync(User user, CancellationToken token)
{
    await _db.SaveAsync(user);                    // ❌ 토큰 누락
    await _http.PostAsync(url, content);          // ❌ 토큰 누락
    token.ThrowIfCancellationRequested();          // 너무 늦음 &amp;mdash; 이미 다 했음
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 취소 신호가 와도 DB 저장과 HTTP POST가 모두 완료된 후에야 예외를 던집니다. 작업 자체는 취소되지 않았습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;올바른 패턴: 모든 호출에 토큰 전달&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async Task SaveUserAsync(User user, CancellationToken token)
{
    await _db.SaveAsync(user, token);
    await _http.PostAsync(url, content, token);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;BCL 비동기 메서드는 거의 모두 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;을 마지막 매개변수로 받는 오버로드를 제공합니다. 누락하면 그 메서드는 취소되지 않습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt; 사용 시&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;stata&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public Task&amp;lt;int&amp;gt; ComputeAsync(int[] data, CancellationToken token)
{
    return Task.Run(() =&amp;gt;
    {
        int sum = 0;
        foreach (var x in data)
        {
            token.ThrowIfCancellationRequested();   // 람다 안에서 사용
            sum += Compute(x);
        }
        return sum;
    }, token);   // &amp;larr; Task.Run 자체에도 전달
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt;의 두 번째 인자로 토큰을 전달하면, 작업이 스케줄러 큐에 있는 동안 취소가 요청되면 &lt;b&gt;시작도 안 하고&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Canceled&lt;/code&gt; 상태로 끝납니다. 람다 안의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested&lt;/code&gt;는 시작된 후의 취소를 처리합니다. 둘은 서로 다른 시점이므로 둘 다 필요합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken.None&lt;/code&gt; &amp;mdash; 취소 불가를 명시적으로&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async Task LoadConfigAsync()
{
    // 설정 로드는 취소되어선 안 됨 &amp;mdash; 명시적으로 None 전달
    await _config.LoadAsync(CancellationToken.None);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;null&lt;/code&gt;이나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;default&lt;/code&gt;를 넘기는 대신 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken.None&lt;/code&gt;을 사용하면 의도가 분명해집니다. 코드 리뷰 시 &quot;왜 토큰을 안 넘겼지?&quot;라는 질문을 막을 수 있습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 11 ===================== --&gt;
&lt;h2 id=&quot;section-11&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;9. Register 콜백 &amp;mdash; 토큰을 모르는 코드에 끼우기&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;오래된 라이브러리나 비관리 API는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;을 받지 않습니다. 이때 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Register&lt;/code&gt;로 취소 시점에 직접 끊을 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async Task&amp;lt;byte[]&amp;gt; ReadLegacyAsync(Stream stream, CancellationToken token)
{
    // 이 Read는 토큰을 받지 않음 &amp;mdash; Register로 우회
    using CancellationTokenRegistration reg = token.Register(() =&amp;gt; stream.Close());

    try
    {
        var buf = new byte[4096];
        int read = await stream.ReadAsync(buf, 0, buf.Length);   // 토큰 없이 호출
        return buf.AsSpan(0, read).ToArray();
    }
    catch (ObjectDisposedException) when (token.IsCancellationRequested)
    {
        throw new OperationCanceledException(token);
    }
    // using으로 reg 자동 Dispose
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;주의사항&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;콜백은 어느 스레드에서 실행될지 모릅니다&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Cancel()&lt;/code&gt;을 부른 스레드일 수도, 별도 스레드일 수도 있습니다. 스레드 안전성을 보장하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;콜백은 짧고 빠르게&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Register&lt;/code&gt; 콜백 내부에서 무거운 작업을 하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Cancel()&lt;/code&gt; 호출자가 막힙니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt; 필수&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenRegistration&lt;/code&gt;을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;하지 않으면 콜백이 CTS의 콜백 리스트에 영구히 남습니다. CTS가 장수명일수록 누수가 심각해집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이미 취소된 토큰에 등록하면 즉시 실행&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Register&lt;/code&gt;를 호출하는 그 스레드에서 동기적으로 콜백이 실행됩니다. 락을 잡고 있다면 데드락 위험이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 12 ===================== --&gt;
&lt;h2 id=&quot;section-12&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;10. Unity 실전 활용&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;MonoBehaviour 수명과 결합 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;destroyCancellationToken&lt;/code&gt;&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 2022.2부터 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MonoBehaviour&lt;/code&gt;에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;destroyCancellationToken&lt;/code&gt; 프로퍼티가 추가됐습니다. 이 토큰은 GameObject가 파괴될 때 자동으로 취소되므로, 비동기 작업이 파괴된 객체에 접근해 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MissingReferenceException&lt;/code&gt;을 던지는 사고를 막을 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using UnityEngine;
using System.Threading.Tasks;

public class EnemySpawner : MonoBehaviour
{
    async void Start()
    {
        try
        {
            await SpawnLoopAsync(destroyCancellationToken);
        }
        catch (System.OperationCanceledException) { /* GameObject 파괴됨 */ }
    }

    async Task SpawnLoopAsync(System.Threading.CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            Spawn();
            await Task.Delay(2000, token);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;destroyCancellationToken&lt;/code&gt;을 사용하지 않으면 씬 전환 후에도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Delay&lt;/code&gt;가 살아 다음 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Spawn()&lt;/code&gt;이 호출되며, 이 시점에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;this.transform&lt;/code&gt;이 이미 파괴되어 예외가 발생합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;UniTask와의 결합&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;UniTask는 Unity 환경에 최적화된 비동기 라이브러리이며, 거의 모든 메서드가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;을 받습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using Cysharp.Threading.Tasks;
using System.Threading;

public class Loader : MonoBehaviour
{
    public async UniTaskVoid LoadAsync()
    {
        var token = this.GetCancellationTokenOnDestroy();   // UniTask 확장
        var prefab = await Resources.LoadAsync&amp;lt;GameObject&amp;gt;(&quot;Boss&quot;)
                                    .ToUniTask(cancellationToken: token);
        Instantiate(prefab);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetCancellationTokenOnDestroy&lt;/code&gt;는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;destroyCancellationToken&lt;/code&gt;과 동일한 의미를 갖는 UniTask 확장입니다. UniTask 1.x 시절 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;destroyCancellationToken&lt;/code&gt;이 BCL에 없던 시기를 보완합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;게임 전체 종료 토큰&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class GameLifetime : MonoBehaviour
{
    public static CancellationTokenSource ApplicationExitCts { get; } = new();

    void OnApplicationQuit() =&amp;gt; ApplicationExitCts.Cancel();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 패턴으로 만든 토큰을 백그라운드 작업(서버 폴링, 자동 저장 등)에 전달하면 게임 종료 시 모든 작업이 일제히 취소돼 깔끔하게 종료됩니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;Unity에서 흔한 함정&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 사용 시 메인 스레드 컨텍스트 미보장&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Delay&lt;/code&gt; 후 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;transform&lt;/code&gt; 접근은 메인 스레드여야 합니다. UniTask는 이를 자동 처리하지만 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;는 보장 안 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토큰 누락&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await SomeApi()&lt;/code&gt; 형태로 토큰을 빼먹으면 씬 전환 후에도 코드가 살아 사고를 일으킵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;취소 예외 무시&lt;/b&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;catch (Exception)&lt;/code&gt;으로 모든 예외를 잡으면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;도 잡히지만, 이를 그냥 무시하면 안 됩니다. 리소스 정리 코드는 반드시 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;finally&lt;/code&gt;에서 실행해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 13 ===================== --&gt;
&lt;h2 id=&quot;section-13&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;11. 일반적인 함정 정리&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;1) 토큰을 받았는데 안 쓰기&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async Task DoAsync(CancellationToken token)   // 시그니처에는 있음
{
    await SomethingAsync();   // ❌ 토큰을 안 넘김
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;코드 리뷰에서 &quot;왜 토큰을 안 넘기죠?&quot;라는 질문이 나와야 정상입니다. Roslyn 분석기 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CA2016&lt;/code&gt; 규칙이 이를 잡아냅니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2) &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt; 누락한 CTS의 콜백 누수&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public void Subscribe(CancellationToken token, Action callback)
{
    token.Register(callback);   // ❌ Dispose하지 않음
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;CTS가 오래 살수록 누수가 심합니다. 반드시 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenRegistration&lt;/code&gt;을 보관하고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;하세요.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3) Cancel을 동기적으로 호출하는 락 안&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;sqf&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;lock (_lock)
{
    _cts.Cancel();   // ❌ Register 콜백이 같은 스레드에서 실행될 수 있음
                     //    콜백 내부에서 _lock을 잡으려 하면 데드락
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;해결책: 락 밖에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Cancel&lt;/code&gt;을 호출하거나, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Cancel(throwOnFirstException: false)&lt;/code&gt; + 별도 스레드 사용.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4) 취소 예외를 무시(swallow)&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;try { await DoAsync(token); }
catch { }   // ❌ 취소 정보가 위로 전파되지 않음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;상위 코드에서는 작업이 정상 완료된 줄 알고 후속 작업을 진행합니다. 최소한 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;은 다시 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;throw&lt;/code&gt;하세요.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5) &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;을 필드로 저장 + 재사용&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;class Service
{
    private CancellationToken _token;   // ❌ 한 번 취소되면 영구히 취소 상태
    public void Init(CancellationToken token) =&amp;gt; _token = token;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;토큰은 한 번 취소되면 다시 살릴 수 없습니다. 재시작이 필요한 시나리오에서는 새 CTS를 만드세요.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 14 ===================== --&gt;
&lt;h2 id=&quot;section-14&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;12. 빠른 점검 체크리스트&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;확인&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;토큰을 받은 모든 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드는 하위 호출에 토큰을 전달하나요?&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;루프 안에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThrowIfCancellationRequested&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCancellationRequested&lt;/code&gt; 검사가 있나요?&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenSource&lt;/code&gt;는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;using&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;로 정리되나요?&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancelAfter&lt;/code&gt;로 만든 CTS도 마찬가지인가요?&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OperationCanceledException&lt;/code&gt;을 일반 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception&lt;/code&gt;과 분리해 처리하나요?&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;타임아웃과 호출자 취소를 구분해야 한다면 Linked Token + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;when&lt;/code&gt; 필터를 사용하나요?&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;Unity에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;destroyCancellationToken&lt;/code&gt;을 사용하나요?&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Register&lt;/code&gt; 콜백은 짧고 스레드 안전하며 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Dispose&lt;/code&gt;되나요?&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 15 ===================== --&gt;
&lt;h2 id=&quot;section-15&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;13. 한 걸음 더&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken.None&lt;/code&gt; vs &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;default&lt;/code&gt;&lt;/b&gt;: 둘 다 같은 토큰이지만 의도 표현은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;None&lt;/code&gt;이 명확합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WaitAsync(token)&lt;/code&gt;&lt;/b&gt; (.NET 6+): 기존 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;에 취소를 덧붙입니다. 호출자가 이미 시작된 작업을 취소하고 싶을 때 유용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;PeriodicTimer&lt;/code&gt;&lt;/b&gt; (.NET 6+): &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitForNextTickAsync(token)&lt;/code&gt;이 토큰을 받아 깔끔하게 취소됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Channel&amp;lt;T&amp;gt;.Reader.ReadAsync(token)&lt;/code&gt;&lt;/b&gt;: 채널 기반 파이프라인에서도 토큰 전파가 필수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 16 ===================== --&gt;
&lt;h2 id=&quot;section-16&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;14. 마무리&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;협조적 취소는 &quot;작업을 강제로 죽일 수 없다&quot;는 제약에서 출발해 &quot;안전하게 종료할 권한을 작업 측에 둔다&quot;는 결론에 도달한 설계입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenSource&lt;/code&gt;는 신호기, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;은 수신기, 그리고 작업 코드는 신호를 듣고 스스로 정리하는 작업자입니다. 이 세 역할을 분명히 인식하면, 토큰을 어디까지 넘겨야 하는지&amp;middot;어떤 시점에 확인해야 하는지&amp;middot;예외를 어떻게 처리해야 하는지가 자연스럽게 따라옵니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 단순합니다 &amp;mdash; &lt;b&gt;토큰을 받았으면 끝까지 넘기고, 예외를 만나면 정상 취소로 인정하고, CTS는 반드시 정리합니다.&lt;/b&gt; 이 세 가지만 지키면 멀티스레드&amp;middot;비동기&amp;middot;UI 응답성&amp;middot;리소스 누수 문제가 한꺼번에 해결됩니다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>C#</category>
      <category>기초</category>
      <category>입문</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/579</guid>
      <comments>https://everyday-devup.tistory.com/579#entry579comment</comments>
      <pubDate>Sat, 9 May 2026 00:10:15 +0900</pubDate>
    </item>
    <item>
      <title>[PART13.비동기와 스레딩 기초(8/15)] 여러 Task 조합 &amp;mdash; Task.WhenAll &amp;middot; Task.WhenAny</title>
      <link>https://everyday-devup.tistory.com/578</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(8/15)] 여러 Task 조합 &amp;mdash; Task.WhenAll &amp;middot; Task.WhenAny&lt;/h1&gt;
&lt;p style=&quot;color: #868e96; font-size: 14px; margin: 0 0 32px; line-height: 1.6;&quot; data-ke-size=&quot;size16&quot;&gt;여러 비동기 작업을 한 번에 묶는 두 도구 / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;은 모두 끝날 때까지 / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;는 가장 먼저 끝나는 하나만 / 타임아웃 패턴과 순차 대기의 함정&lt;/p&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;문제 제기 &amp;mdash; `await` 한 번에 하나씩만 기다리면 생기는 일&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;개념 정의 &amp;mdash; 두 가지 조합기(Combinator)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;내부 동작 &amp;mdash; 게이트키퍼 Task와 카운트다운&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;실전 적용 &amp;mdash; 언제 어떻게 쓰는가&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;함정과 주의사항&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;C# 버전별 변화&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;정리&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 제기 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 한 번에 하나씩만 기다리면 생기는 일&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 모바일 게임에서 게임이 시작되기 전 다음 세 가지가 모두 끝나야 한다고 해봅시다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐릭터 모델 에셋 로드 (300ms)&lt;/li&gt;
&lt;li&gt;레벨 프리팹 로드 (250ms)&lt;/li&gt;
&lt;li&gt;서버에서 플레이어 데이터 조회 (400ms)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;신입 개발자가 가장 먼저 떠올리는 코드는 보통 이렇습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task LoadGameAsync()
{
    var character = await LoadCharacterAsync();   // 300ms 대기
    var level     = await LoadLevelAsync();       // 250ms 대기
    var player    = await FetchPlayerDataAsync(); // 400ms 대기
    // 총 950ms
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;각 줄은 정상 동작하지만, 세 작업은 &lt;b&gt;서로 의존하지 않습니다.&lt;/b&gt; 그런데도 위 코드는 약 &lt;b&gt;950ms&lt;/b&gt;가 걸립니다. 첫 번째 작업이 끝날 때까지 두 번째를 시작조차 하지 않고, 두 번째가 끝날 때까지 세 번째를 시작조차 하지 않기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;세 작업을 동시에 시작했다면 가장 오래 걸리는 작업의 시간(400ms)만큼만 기다리면 됩니다. 무려 &lt;b&gt;550ms&lt;/b&gt;의 로딩 시간을 그냥 버린 셈입니다. 모바일 게임에서는 로딩 화면 1초가 사용자 이탈의 직접적인 원인이 됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;또 다른 시나리오도 있습니다. 서버에 요청을 보냈는데 응답이 5초가 지나도 오지 않으면 &lt;b&gt;타임아웃&lt;/b&gt; 처리하고 사용자에게 재시도 버튼을 보여줘야 합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;만으로는 이 &quot;기다리는 시간 자체에 제한을 거는&quot; 동작을 표현할 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 두 가지 문제 &amp;mdash; &lt;b&gt;여러 작업을 병렬로 묶기&lt;/b&gt;와 &lt;b&gt;타임아웃을 거는 가장 먼저 끝난 작업 고르기&lt;/b&gt; &amp;mdash; 를 동시에 해결하는 도구가 바로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;과 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAny&lt;/code&gt;입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;2. 개념 정의 &amp;mdash; 두 가지 조합기(Combinator)&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2.1 비유: 음식 배달 주문&lt;/h3&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt; &amp;mdash; 단체 회식&lt;/b&gt; 친구 다섯 명에게 각자 음식을 주문하라고 시키고, &lt;b&gt;모두가 자기 음식을 받을 때까지 기다렸다가&lt;/b&gt; 다같이 식사를 시작합니다. 한 명이라도 늦으면 그 사람이 도착할 때까지 기다립니다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAny&lt;/code&gt; &amp;mdash; 가장 빠른 배달&lt;/b&gt; 같은 메뉴를 세 가게에 주문하고, &lt;b&gt;가장 먼저 도착한 가게의 음식만 받습니다.&lt;/b&gt; 나머지 두 곳은 도착해도 무시(또는 취소)합니다.&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이름 그대로입니다 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;은 &quot;모두 끝날 때(when all)&quot;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;는 &quot;어느 하나라도 끝날 때(when any)&quot; 완료됩니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2.2 시각화&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/B6PCn/dJMcahYD7DZ/AAAAAAAAAAAAAAAAAAAAAGn1vzzRZ6ZzEX19FgADmeYeB6b9Fox5H3qdyLRt2urn/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=9ZgpcYtZwBI0OSQ9JQFCwUTvJMo%3D&quot; alt=&quot;Task.WhenAll &amp;mdash; 모두 끝날 때까지&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;은 가장 느린 작업의 시간만큼, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;는 가장 빠른 작업의 시간만큼 걸립니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2.3 가장 단순한 사용법&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Threading.Tasks;

async Task&amp;lt;int[]&amp;gt; WhenAllExampleAsync()
{
    Task&amp;lt;int&amp;gt; a = Task.FromResult(10);
    Task&amp;lt;int&amp;gt; b = Task.FromResult(20);
    Task&amp;lt;int&amp;gt; c = Task.FromResult(30);

    int[] results = await Task.WhenAll(a, b, c);
    // results = [10, 20, 30]  &amp;mdash; 입력 순서 그대로
    return results;
}

async Task&amp;lt;int&amp;gt; WhenAnyExampleAsync()
{
    Task&amp;lt;int&amp;gt; fast = Task.Delay(50).ContinueWith(_ =&amp;gt; 1);
    Task&amp;lt;int&amp;gt; slow = Task.Delay(500).ContinueWith(_ =&amp;gt; 2);

    Task&amp;lt;int&amp;gt; first = await Task.WhenAny(fast, slow); // Task&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt; 를 await하면 Task&amp;lt;int&amp;gt;
    // first 는 fast 와 같은 객체
    return await first; // 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; &amp;mdash; 비동기 대기 키워드&lt;/b&gt; 비동기 작업이 끝날 때까지 현재 메서드를 일시 중지(suspend)하고, 호출 스레드는 다른 일을 할 수 있도록 반환한다. 작업이 끝나면 다시 이어서 실행한다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;int[] r = await Task.WhenAll(tasks);&lt;/code&gt; tasks 안의 모든 Task가 끝날 때까지 메서드가 멈췄다가 결과 배열을 받는다.&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;은 결과 배열(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;T[]&lt;/code&gt;)을 돌려주고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;는 &lt;b&gt;완료된 Task 자체&lt;/b&gt;를 돌려줍니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;로 결과 값을 얻으려면 한 번 더 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;해야 합니다 &amp;mdash; 이 점이 처음 보면 가장 헷갈립니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2.4 반환 타입 정리&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;메서드&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;입력&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;반환 타입&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll(Task[])&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 여러 개&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&amp;lt;T&amp;gt;(Task&amp;lt;T&amp;gt;[])&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 여러 개&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T[]&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;T[]&lt;/code&gt; 배열&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAny(Task[])&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 여러 개&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;Task&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;완료된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAny&amp;lt;T&amp;gt;(Task&amp;lt;T&amp;gt;[])&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 여러 개&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;완료된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;이 입력 순서대로 결과를 묶어주는 점이 중요합니다. 가장 빨리 끝난 Task가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;results[0]&lt;/code&gt;이 되는 게 아니라, &lt;b&gt;입력 리스트의 첫 번째 Task의 결과&lt;/b&gt;가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;results[0]&lt;/code&gt;입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2.5 IL 분석 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAll(...)&lt;/code&gt;은 다른 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;와 똑같다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ParallelAsync&lt;/code&gt;라는 메서드를 컴파일해 IL을 보면, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;은 그냥 평범한 메서드 호출이고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 상태 머신을 만든다는 사실이 분명해집니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;private static async Task&amp;lt;int[]&amp;gt; ParallelAsync(List&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt; tasks)
{
    return await Task.WhenAll(tasks);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ParallelAsync 의 컴파일된 메서드 본체
.method private hidebysig static
    class [System.Runtime]System.Threading.Tasks.Task`1&amp;lt;int32[]&amp;gt;
    ParallelAsync(class [...]List`1&amp;lt;class [...]Task`1&amp;lt;int32&amp;gt;&amp;gt; tasks) cil managed
{
    // 1. 상태 머신 구조체를 스택에 만들고
    .locals init ([0] valuetype Program/'&amp;lt;ParallelAsync&amp;gt;d__1' V_0)

    ldloca.s   V_0
    call       valuetype [...]AsyncTaskMethodBuilder`1&amp;lt;int32[]&amp;gt;::Create()
    stfld      ...t__builder            // 빌더 초기화
    ldloca.s   V_0
    ldarg.0
    stfld      ...tasks                 // 파라미터 저장
    ldloca.s   V_0
    ldc.i4.m1
    stfld      ...&amp;lt;&amp;gt;1__state            // state = -1 (시작 전)

    // 2. 상태 머신 시작
    ldloca.s   V_0
    ldflda     ...t__builder
    ldloca.s   V_0
    call       AsyncTaskMethodBuilder`1::Start&amp;lt;...&amp;gt;(!!0&amp;amp;)
    ldloca.s   V_0
    ldflda     ...t__builder
    call       AsyncTaskMethodBuilder`1::get_Task()
    ret
}

// 상태 머신 내부 MoveNext 의 핵심 (Task.WhenAll 호출과 await 처리)
IL_00ae: call class [...]Task`1&amp;lt;int32[]&amp;gt; System.Threading.Tasks.Task::WhenAll(...)
IL_00b3: callvirt instance valuetype TaskAwaiter`1&amp;lt;int32[]&amp;gt; Task`1::GetAwaiter()
IL_00b8: stloc.3
IL_00bb: call instance bool TaskAwaiter`1::get_IsCompleted()
IL_00c0: brtrue.s IL_0101                // 이미 완료면 동기 경로
// &amp;darr; 미완료면 콜백 등록 + leave (호출자에게 제어권 반환)
IL_00d8: ldloca.s 3
IL_00db: call instance void AsyncTaskMethodBuilder::AwaitUnsafeOnCompleted&amp;lt;...&amp;gt;(...)
IL_00e0: leave IL_018d
// &amp;darr; 재진입 시 결과 추출
IL_0103: call instance !0 TaskAwaiter`1::GetResult()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 두 줄입니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL_00ae&lt;/code&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll(tasks)&lt;/code&gt;는 그저 정적 메서드 호출이다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;int[]&amp;gt;&lt;/code&gt;를 반환할 뿐 마법은 없다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL_00bb&lt;/code&gt; ~ &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL_00db&lt;/code&gt;: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 만든 상태 머신이 그 Task에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetAwaiter&lt;/code&gt; &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCompleted&lt;/code&gt; 체크 &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AwaitUnsafeOnCompleted&lt;/code&gt;로 콜백 등록을 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAll(tasks)&lt;/code&gt;는 컴파일 관점에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await someOrdinaryTask&lt;/code&gt;와 완전히 동일한 모양입니다. 차이는 단지 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;someOrdinaryTask&lt;/code&gt;가 &quot;여러 Task가 모두 끝나면 완료되는 게이트키퍼 Task&quot;라는 점뿐입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;3. 내부 동작 &amp;mdash; 게이트키퍼 Task와 카운트다운&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3.1 시각화&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/DsegM/dJMcahYD7D0/AAAAAAAAAAAAAAAAAAAAAO1CWTdL8wvmMUmwZjIsr0zSygYN9SJxT-NvbKzmIL64/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=cr0%2F%2FMSQqYBVImMQ6oZU2FBsVwc%3D&quot; alt=&quot;WhenAll 내부 &amp;mdash; 카운트다운으로 모두 끝나기를 기다린다&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3.2 동작 단계&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;을 호출하는 순간 일어나는 일을 단계별로 보면 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;게이트키퍼 Task 생성&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;이 우리에게 돌려주는 그 Task다. 아직 완료되지 않은 상태.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컨티뉴에이션(continuation) 등록&lt;/b&gt; &amp;mdash; 입력된 모든 Task에 &quot;이 Task가 끝나면 게이트키퍼에게 알려달라&quot;는 콜백을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ContinueWith&lt;/code&gt;로 단다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;카운트다운&lt;/b&gt; &amp;mdash; 내부 카운터를 입력 Task 개수로 초기화. Task가 하나 끝날 때마다 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.Decrement&lt;/code&gt;로 1씩 감소.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모두 완료 처리&lt;/b&gt; &amp;mdash; 카운터가 0이 되면 게이트키퍼의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Result&lt;/code&gt;에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;T[]&lt;/code&gt; 배열을 채우고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;RanToCompletion&lt;/code&gt; 상태로 전환. 예외가 있었으면 모아서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt;으로 묶어 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Faulted&lt;/code&gt; 상태로 전환.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;는 더 단순합니다 &amp;mdash; 컨티뉴에이션은 등록하지만, &lt;b&gt;첫 번째로 도착한 Task만&lt;/b&gt; 게이트키퍼의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Result&lt;/code&gt;로 설정합니다. 두 번째, 세 번째 Task가 끝나도 이미 완료된 게이트키퍼에 다시 쓰지 못하고 무시됩니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked&lt;/code&gt; &amp;mdash; 원자적 연산 클래스&lt;/b&gt; 멀티스레드 환경에서 변수의 읽기&amp;middot;증감&amp;middot;교체를 인터럽트 없이 한 번에 수행한다. 락 없이도 안전한 카운트가 필요할 때 쓴다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Interlocked.Decrement(ref remaining)&lt;/code&gt; 여러 Task가 동시에 완료돼도 카운트가 꼬이지 않는다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3.3 결과 배열의 순서 보장&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;이 돌려주는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;T[]&lt;/code&gt;는 &lt;b&gt;입력 순서&lt;/b&gt;를 보장합니다 &amp;mdash; 가장 빨리 끝난 Task가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;[0]&lt;/code&gt;이 되는 게 아니라 입력 리스트의 첫 번째 Task의 결과가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;[0]&lt;/code&gt;입니다. 이는 내부 구현이 입력 배열의 인덱스를 그대로 결과 배열의 인덱스로 사용하기 때문입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task DemoOrderAsync()
{
    var slow = Task.Run(async () =&amp;gt; { await Task.Delay(300); return &quot;slow&quot;; });
    var fast = Task.Run(async () =&amp;gt; { await Task.Delay(50);  return &quot;fast&quot;; });

    string[] results = await Task.WhenAll(slow, fast);
    // results[0] == &quot;slow&quot;  &amp;larr; 입력 순서대로
    // results[1] == &quot;fast&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3.4 IL 관점 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;은 평범한 메서드, 마법은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;에 있다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;위 IL에서 본 것처럼 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll(...)&lt;/code&gt; 자체는 &lt;b&gt;그냥 정적 메서드 호출&lt;/b&gt;입니다. 게이트키퍼 Task 객체를 만들어 돌려주는 일만 합니다. 그 Task를 비동기적으로 기다리는 메커니즘(상태 머신, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNext&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AwaitUnsafeOnCompleted&lt;/code&gt;)은 모두 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 키워드가 컴파일러를 통해 만든 것입니다. 이 사실 덕분에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;이 돌려준 Task를 변수에 담아 두었다가 나중에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;해도, 다른 메서드에 인자로 넘겨도 모두 자연스럽게 동작합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;4. 실전 적용 &amp;mdash; 언제 어떻게 쓰는가&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.1 Before/After: 순차 대기 vs 병렬 대기&lt;/h3&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;Before &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;foreach&lt;/code&gt;로 하나씩 await (병렬 효과 사라짐)&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task&amp;lt;int[]&amp;gt; SequentialAsync(List&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt; tasks)
{
    var results = new int[tasks.Count];
    for (int i = 0; i &amp;lt; tasks.Count; i++)
    {
        results[i] = await tasks[i]; // &amp;larr; 한 개씩만 기다림
    }
    return results;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 시나리오: 에셋 5개 로드. 각 작업이 200ms씩 걸리면 &lt;b&gt;총 1000ms&lt;/b&gt;.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;After &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;로 한 번에 (병렬 실행)&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task&amp;lt;int[]&amp;gt; ParallelAsync(List&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt; tasks)
{
    return await Task.WhenAll(tasks); // &amp;larr; 동시에 기다림
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;같은 시나리오: 5개가 동시에 진행되므로 &lt;b&gt;총 약 200ms&lt;/b&gt;. 5배 빠름.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;IL 분석 &amp;mdash; 두 패턴의 결정적 차이&lt;/h4&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SequentialAsync&lt;/code&gt;의 IL을 보면 루프 한 바퀴마다 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 코드(상태 머신 분기 + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AwaitUnsafeOnCompleted&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;leave&lt;/code&gt;)가 &lt;b&gt;반복적으로&lt;/b&gt; 실행되도록 컴파일됩니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// SequentialAsync 의 MoveNext 일부 &amp;mdash; 루프 안의 await
IL_004x: ldarg.0
IL_004y: ldloc.x                         // tasks[i]
IL_004z: callvirt Task`1::GetAwaiter()
IL_005x: stloc.x
IL_005y: call    TaskAwaiter`1::get_IsCompleted()
IL_005z: brtrue.s ...                    // 동기 경로
IL_006x: call    AwaitUnsafeOnCompleted  // &amp;larr; 루프마다 콜백 등록
IL_006y: leave   ...                     // &amp;larr; 매 반복마다 호출자에게 양보
// 다음 반복에서 GetResult 후 results[i] = ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;즉 IL 레벨에서 보면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SequentialAsync&lt;/code&gt;는 &quot;Task를 하나 기다림 &amp;rarr; 결과 저장 &amp;rarr; 다음 Task를 기다림&quot;이 N번 반복됩니다. 그래서 시간이 더해집니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;반면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ParallelAsync&lt;/code&gt;의 IL은 단 한 번의 await만 있습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;avrasm&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ParallelAsync 의 MoveNext 일부
IL_00ae: call    Task::WhenAll(...)      // 게이트키퍼 Task 생성 (즉시 반환)
IL_00b3: callvirt Task`1::GetAwaiter()
IL_00bb: call    TaskAwaiter`1::get_IsCompleted()
IL_00c0: brtrue.s IL_0101
IL_00db: call    AwaitUnsafeOnCompleted // &amp;larr; 콜백 등록은 단 한 번
IL_00e0: leave   IL_018d
IL_0103: call    TaskAwaiter`1::GetResult()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll(tasks)&lt;/code&gt; 호출은 &lt;b&gt;게이트키퍼 Task를 즉시 반환&lt;/b&gt;합니다. 게이트키퍼는 카운트다운 카운터가 0이 될 때만 완료됩니다. 이 한 번의 await가 N개 작업의 &lt;b&gt;동시 진행 시간&lt;/b&gt;을 측정합니다 &amp;mdash; 그래서 Max(작업 시간들)만큼만 걸립니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.2 타임아웃 패턴 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny + Task.Delay&lt;/code&gt;&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;의 가장 흔한 용도는 타임아웃입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task&amp;lt;string&amp;gt; FetchWithTimeoutAsync(int timeoutMs)
{
    Task&amp;lt;string&amp;gt; work  = FetchFromServerAsync();
    Task         delay = Task.Delay(timeoutMs);

    Task first = await Task.WhenAny(work, delay);

    if (first == delay)
        throw new TimeoutException($&quot;{timeoutMs}ms 초과&quot;);

    return await work; // 서버 응답 결과
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;게이트키퍼 Task가 어느 쪽이 먼저 끝났는지 알려주므로 &lt;b&gt;참조 비교&lt;/b&gt;(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;first == delay&lt;/code&gt;)로 분기합니다. .NET 6+에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;task.WaitAsync(TimeSpan)&lt;/code&gt;라는 더 짧은 API도 있지만, 내부 동작 이해를 위해 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny + Delay&lt;/code&gt; 패턴을 알아두는 게 좋습니다.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;IL 분석 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt; 호출과 참조 비교&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;private static async Task&amp;lt;int&amp;gt; WithTimeoutAsync(Task&amp;lt;int&amp;gt; work, int timeoutMs)
{
    Task delay = Task.Delay(timeoutMs);
    if (await Task.WhenAny(work, delay) == delay)
        throw new TimeoutException();
    return await work;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;gradle&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// MoveNext 핵심 부분 &amp;mdash; WhenAny 호출과 참조 비교
ldarg.0
ldfld    ...work
ldarg.0
ldfld    ...delay
call     class Task Task::WhenAny(class Task, class Task)  // &amp;larr; 두 인자 버전
callvirt TaskAwaiter`1::GetAwaiter()
// ... await 상태 머신 (생략) ...
call     TaskAwaiter`1::GetResult()                        // &amp;larr; 완료된 Task 반환
ldarg.0
ldfld    ...delay
ceq                                                         // &amp;larr; 참조 비교 (==)
brfalse.s ... (work 결과 반환 경로)
newobj   instance void TimeoutException::.ctor()
throw&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ceq&lt;/code&gt;가 핵심입니다 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;가 돌려준 Task와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;delay&lt;/code&gt; Task의 &lt;b&gt;참조&lt;/b&gt;를 비교합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;는 &quot;방금 완료된 Task의 참조 그 자체&quot;를 결과로 주기 때문에 어느 쪽이 먼저 끝났는지 알 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.3 Unity 실전 &amp;mdash; 씬 로딩 병렬화&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 모바일 게임에서 씬 진입 시 자주 마주치는 패턴입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class SceneLoader : MonoBehaviour
{
    public async Task EnterStageAsync(string stageId)
    {
        // 1. 모든 비동기 작업을 동시에 시작 (await 하지 않음)
        Task&amp;lt;GameObject&amp;gt;      characterTask = LoadAssetAsync&amp;lt;GameObject&amp;gt;(&quot;character&quot;);
        Task&amp;lt;GameObject&amp;gt;      stageTask     = LoadAssetAsync&amp;lt;GameObject&amp;gt;($&quot;stage_{stageId}&quot;);
        Task&amp;lt;AudioClip&amp;gt;       bgmTask       = LoadAssetAsync&amp;lt;AudioClip&amp;gt;($&quot;bgm_{stageId}&quot;);
        Task&amp;lt;PlayerSaveData&amp;gt;  saveTask      = ServerApi.FetchSaveAsync();

        // 2. 한 번에 기다림 &amp;mdash; 가장 오래 걸리는 작업의 시간만큼만 걸림
        await Task.WhenAll(characterTask, stageTask, bgmTask, saveTask);

        // 3. 결과 사용 (이미 완료됐으므로 .Result 안전)
        Instantiate(characterTask.Result);
        Instantiate(stageTask.Result);
        AudioSource.PlayClipAtPoint(bgmTask.Result, Vector3.zero);
        ApplySaveData(saveTask.Result);
    }

    static Task&amp;lt;T&amp;gt; LoadAssetAsync&amp;lt;T&amp;gt;(string key) where T : Object
    {
        var op = Addressables.LoadAssetAsync&amp;lt;T&amp;gt;(key);
        return op.Task; // Addressables는 Task로 변환 가능
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncOperationHandle.Task&lt;/code&gt; 속성을 사용하면 Addressables 로딩을 표준 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;로 다룰 수 있습니다. 그 이후로는 일반 C# 비동기와 동일하게 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;로 묶을 수 있습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #fff5f5; border-left: 4px solid #fa5252; border-radius: 0 8px 8px 0; padding: 14px 18px; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;주의:&lt;/b&gt; Unity 메인 스레드 동기화 컨텍스트(SynchronizationContext)는 IL2CPP&amp;middot;플랫폼&amp;middot;Unity 버전에 따라 동작이 다릅니다. 라이브러리 코드라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAll(...).ConfigureAwait(false)&lt;/code&gt;도 고려할 수 있지만, MonoBehaviour 코드에서 await 이후에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Transform&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Instantiate&lt;/code&gt; 같은 메인 스레드 API를 호출해야 한다면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(false)&lt;/code&gt;를 쓰면 안 됩니다 &amp;mdash; 메인 스레드로 돌아오지 않을 수 있습니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.4 Unity 실전 &amp;mdash; 가장 빠른 서버 선택&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;로 여러 후보 서버에 동시에 핑을 보내고 가장 먼저 응답한 곳을 선택할 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task&amp;lt;string&amp;gt; PickFastestServerAsync(string[] candidates)
{
    using var cts = new CancellationTokenSource();
    var pingTasks = new Task&amp;lt;string&amp;gt;[candidates.Length];

    for (int i = 0; i &amp;lt; candidates.Length; i++)
    {
        pingTasks[i] = PingAsync(candidates[i], cts.Token);
    }

    Task&amp;lt;string&amp;gt; winner = await Task.WhenAny(pingTasks);

    cts.Cancel(); // 나머지 서버 ping은 취소 (네트워크 낭비 방지)

    return await winner;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenSource.Cancel()&lt;/code&gt;을 호출해 진행 중인 나머지 ping을 정리하는 것이 모바일 환경에서 중요합니다 &amp;mdash; 데이터 요금과 배터리를 아낍니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;5. 함정과 주의사항&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.1 함정 1 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt; 다음에 첫 예외만 잡으면 나머지 예외를 놓친다&lt;/h3&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;❌ 잘못된 패턴&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;try
{
    await Task.WhenAll(taskA, taskB, taskC);
}
catch (Exception ex)
{
    // ex 는 첫 번째 예외 하나뿐!
    // 나머지 두 개는 어디로?
    Debug.LogError(ex);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAll&lt;/code&gt;은 여러 Task가 실패해도 &lt;b&gt;첫 번째 예외만 다시 던집니다(rethrow)&lt;/b&gt;. 이는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AggregateException&lt;/code&gt;을 unwrap해서 첫 번째 InnerException을 던지기 때문입니다. 나머지 예외는 게이트키퍼 Task의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception&lt;/code&gt; 속성에는 들어 있지만, 코드에서 명시적으로 꺼내지 않으면 사라집니다.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;✅ 올바른 패턴&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;pgsql&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;Task all = Task.WhenAll(taskA, taskB, taskC);
try
{
    await all;
}
catch
{
    // all.Exception 에는 모든 예외가 AggregateException 으로 담겨 있다
    foreach (var inner in all.Exception!.InnerExceptions)
    {
        Debug.LogError(inner);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;이 돌려준 Task를 &lt;b&gt;변수에 담아두는 것&lt;/b&gt;입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;가 던지는 예외 객체는 첫 번째 것뿐이지만, Task 객체의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception&lt;/code&gt; 속성에는 전부 보존되어 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;IL 관점&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// catch 블록 내부에서 변수에 저장된 Task에 접근
ldloc.0                           // Task all
callvirt Task::get_Exception()    // AggregateException? 반환
callvirt AggregateException::get_InnerExceptions()
// foreach 루프&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;get_Exception()&lt;/code&gt;이 핵심입니다. 이 속성이 모든 예외를 보존하고 있으므로, Task 변수만 살려두면 모든 InnerException을 순회할 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.2 함정 2 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;로 끝나도 나머지 Task는 계속 돈다&lt;/h3&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;❌ 잘못된 인식&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;Task&amp;lt;string&amp;gt; work = FetchAsync();
Task         delay = Task.Delay(2000);

await Task.WhenAny(work, delay);
// &quot;타임아웃이니까 work는 자동으로 멈췄겠지?&quot; &amp;mdash; 아니다!
// work 는 계속 진행 중이며, 끝나면 결과를 버려도 그 사이 자원&amp;middot;돈을 쓴다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;는 어느 하나가 끝났음을 알려줄 뿐, &lt;b&gt;나머지 Task를 멈추지 않습니다.&lt;/b&gt; 네트워크 요청이라면 끝까지 응답을 받으며 그동안 데이터&amp;middot;배터리를 소모합니다.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;✅ 올바른 패턴 &amp;mdash; 취소 토큰 결합&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task&amp;lt;string&amp;gt; FetchWithTimeoutAsync(int timeoutMs)
{
    using var cts = new CancellationTokenSource(timeoutMs);
    try
    {
        // 서버 호출이 cts.Token 을 협조적으로 확인하도록 만든다
        return await FetchAsync(cts.Token);
    }
    catch (OperationCanceledException)
    {
        throw new TimeoutException($&quot;{timeoutMs}ms 초과&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationTokenSource&lt;/code&gt;가 시간을 추적하고, 시간이 지나면 토큰이 취소 신호를 보내며, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;FetchAsync&lt;/code&gt; 내부의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;HttpClient&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UnityWebRequest&lt;/code&gt;&amp;middot;내가 짠 코드가 이 토큰을 협조적으로 체크하면서 정리합니다. 이 방식이 자원 측면에서 훨씬 깔끔합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.3 함정 3 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Wait()&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Result&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAll&lt;/code&gt;은 await가 아니다 (데드락 위험)&lt;/h3&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;❌ 잘못된 패턴&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Unity UI 이벤트 핸들러
public void OnButtonClick()
{
    var task = LoadAssetAsync();
    task.Wait(); // &amp;larr; UI 스레드 블로킹! 게임 화면 멈춤
    // Unity SynchronizationContext에서는 데드락도 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Wait&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;.Result&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WaitAll&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WaitAny&lt;/code&gt;는 &lt;b&gt;현재 스레드를 블로킹&lt;/b&gt;합니다. UI 스레드(또는 Unity 메인 스레드)에서 호출하면 화면이 멈추고, SynchronizationContext가 있는 환경에서는 데드락도 발생합니다.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;✅ 올바른 패턴 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAll/WhenAny&lt;/code&gt;만 사용&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async void OnButtonClick()
{
    var task = LoadAssetAsync();
    await task; // &amp;larr; 메인 스레드 양보, 끝나면 다시 돌아옴
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;메서드&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;동작&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;UI/Unity 메인 스레드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WaitAll(tasks)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;동기&amp;middot;블로킹&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;❌ 절대 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WaitAny(tasks)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;동기&amp;middot;블로킹&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;❌ 절대 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAll(tasks)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;비동기&amp;middot;논블로킹&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅ 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAny(tasks)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;비동기&amp;middot;논블로킹&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅ 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이름이 비슷해서 헷갈리지만 동작은 정반대입니다 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Wait*&lt;/code&gt;은 동기, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;When*&lt;/code&gt;은 비동기.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.4 함정 4 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;를 루프로 돌려 &quot;완료 순서대로 처리&quot;하면 O(N&amp;sup2;)&lt;/h3&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;❌ 잘못된 패턴&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task ProcessInOrderAsync(List&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt; tasks)
{
    while (tasks.Count &amp;gt; 0)
    {
        // N번 호출 &amp;times; 매번 N개 Task에 콜백 등록 = O(N&amp;sup2;)
        Task&amp;lt;int&amp;gt; done = await Task.WhenAny(tasks);
        tasks.Remove(done);
        Process(await done);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;매 루프마다 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;가 남은 모든 Task에 새로운 컨티뉴에이션을 등록합니다. 작업이 100개면 컨티뉴에이션 등록&amp;middot;해제가 약 5000번 발생합니다.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;✅ 올바른 패턴 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenEach&lt;/code&gt; (.NET 9+)&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;async Task ProcessInOrderAsync(IEnumerable&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt; tasks)
{
    await foreach (Task&amp;lt;int&amp;gt; completed in Task.WhenEach(tasks))
    {
        Process(await completed);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;.NET 9에서 도입된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenEach&lt;/code&gt;는 완료되는 순서대로 Task를 비동기 스트림(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;)으로 돌려줍니다. 내부적으로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IValueTaskSource&lt;/code&gt;를 활용해 O(N)으로 동작합니다. 구버전(.NET 8 이하&amp;middot;Unity 2022)에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;TaskCompletionSource&lt;/code&gt;로 직접 구현해야 합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.5 함정 5 &amp;mdash; Unity Profiler에서 GC 스파이크가 보일 때&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll(...)&lt;/code&gt;은 호출할 때마다 다음을 새로 할당합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게이트키퍼 Task 객체 (참조 타입, 힙 할당)&lt;/li&gt;
&lt;li&gt;입력 Task가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IEnumerable&lt;/code&gt;이면 내부적으로 배열로 변환 (할당)&lt;/li&gt;
&lt;li&gt;결과 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;T[]&lt;/code&gt; 배열 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 버전)&lt;/li&gt;
&lt;li&gt;각 Task에 등록되는 컨티뉴에이션 콜백 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;씬 전환&amp;middot;로딩 같이 한 번 일어나는 작업에서는 무시 가능하지만 &lt;b&gt;Update 루프나 매 프레임 호출되는 핫패스&lt;/b&gt;에서 매번 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;을 만들면 GC 스파이크가 발생합니다.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;❌ 잘못된 패턴 &amp;mdash; Update 안에서 매 프레임 WhenAll&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;void Update()
{
    // 매 프레임 WhenAll 호출 &amp;mdash; GC 스파이크의 원인
    _ = Task.WhenAll(SomeWorkAsync(), AnotherWorkAsync());
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;✅ 올바른 패턴 &amp;mdash; 핫패스에서는 Task 자체를 피하거나 ValueTask 사용&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 핫패스에서는 동기 처리
void Update() { DoSyncWork(); }

// 비동기가 꼭 필요하면 한 번만 시작하고 상태 추적
Task? _pending;
void Update()
{
    if (_pending == null || _pending.IsCompleted)
    {
        _pending = Task.WhenAll(SomeWorkAsync(), AnotherWorkAsync());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;또는 Unity 전용 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask&lt;/code&gt; 라이브러리는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 대비 할당이 거의 없도록 설계돼 있어 핫패스에서 더 적합합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;6. C# 버전별 변화&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;버전&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;변화&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;.NET Framework 4.5 (2012)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAny&lt;/code&gt; 도입&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async/await&lt;/code&gt;와 함께 표준 비동기 조합기 등장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;.NET Core 3.0 (2019)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; 도입&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;비동기 스트림이 가능해지면서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt; 루프의 대안 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;.NET 6 (2021)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WaitAsync(TimeSpan)&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WaitAsync(CancellationToken)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;타임아웃 패턴이 더 짧아짐 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny + Delay&lt;/code&gt; 보일러플레이트 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;.NET 9 (2024)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenEach(IEnumerable&amp;lt;Task&amp;gt;)&lt;/code&gt; 도입&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&quot;완료 순서대로 처리&quot; 패턴이 O(N)으로, 비동기 스트림으로 표현됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.1 .NET 5 이하 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny + Delay&lt;/code&gt; 타임아웃&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Before &amp;mdash; .NET 5 이전 (또는 Unity 2022 LTS, .NET Standard 2.1)
async Task&amp;lt;string&amp;gt; FetchWithTimeoutAsync(int timeoutMs)
{
    Task&amp;lt;string&amp;gt; work  = FetchAsync();
    Task         delay = Task.Delay(timeoutMs);

    if (await Task.WhenAny(work, delay) == delay)
        throw new TimeoutException();
    return await work;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.2 .NET 6+ &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAsync&lt;/code&gt;로 한 줄 단축&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// After &amp;mdash; .NET 6+
async Task&amp;lt;string&amp;gt; FetchWithTimeoutAsync(int timeoutMs)
{
    return await FetchAsync().WaitAsync(TimeSpan.FromMilliseconds(timeoutMs));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;IL 관점에서의 차이&lt;/h4&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAsync&lt;/code&gt;는 내부적으로는 여전히 컨티뉴에이션 + 타이머를 사용하지만, IL 레벨에서 보면 호출자 코드에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt; 호출과 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Delay&lt;/code&gt; 객체 할당이 &lt;b&gt;사라집니다.&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;gradle&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Before &amp;mdash; WhenAny 패턴
call class Task Task::Delay(int32)              // delay Task 할당
call class Task Task::WhenAny(class Task, class Task)  // 게이트키퍼 Task 할당
ceq                                              // 참조 비교
// ... 분기 ...

// After &amp;mdash; WaitAsync
call class Task`1&amp;lt;...&amp;gt; Task`1::WaitAsync(valuetype TimeSpan)
// 끝&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;호출자 IL이 짧아지고 할당이 줄어듭니다. Unity 2022 LTS는 .NET Standard 2.1 기반이라 아직 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAsync&lt;/code&gt;가 없을 수 있지만, Unity 6 / .NET 8+ 환경이라면 적극 사용을 권장합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.3 .NET 9 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenEach&lt;/code&gt;로 완료 순서대로 처리&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Before &amp;mdash; WhenAny 루프 (O(N&amp;sup2;))
async Task ProcessAsync(List&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt; tasks)
{
    while (tasks.Count &amp;gt; 0)
    {
        var done = await Task.WhenAny(tasks);
        tasks.Remove(done);
        Console.WriteLine(await done);
    }
}

// After &amp;mdash; WhenEach (O(N), 비동기 스트림)
async Task ProcessAsync(IEnumerable&amp;lt;Task&amp;lt;int&amp;gt;&amp;gt; tasks)
{
    await foreach (Task&amp;lt;int&amp;gt; completed in Task.WhenEach(tasks))
    {
        Console.WriteLine(await completed);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenEach&lt;/code&gt;는 .NET 9 / C# 13 환경(서버&amp;middot;CLI)에서 사용 가능합니다. Unity는 아직 .NET Standard 2.1 기반이므로 직접 도입하지는 못하지만, 향후 이주 시 알아둘 가치가 있습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;7. 정리&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서 다룬 핵심을 한 줄씩 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;#&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;핵심&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt; vs &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;모두 끝날 때까지 vs 가장 먼저 끝나는 하나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;반환 타입&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T[]&amp;gt;&lt;/code&gt; (입력 순서) vs &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;Task&amp;gt;&lt;/code&gt; (완료된 Task 자체)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;순차 vs 병렬&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;foreach (var t in tasks) await t;&lt;/code&gt;는 합산 시간, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAll(tasks)&lt;/code&gt;는 최댓값 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;타임아웃&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAny(work, Task.Delay(timeout))&lt;/code&gt; 또는 .NET 6+의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAsync&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;예외 처리&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAll&lt;/code&gt;은 첫 예외만 던진다 &amp;mdash; 모든 예외는 Task 변수의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception.InnerExceptions&lt;/code&gt;에&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt; 후 정리&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;나머지 Task는 자동으로 안 멈춘다 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;으로 협조 취소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Wait*&lt;/code&gt; 금지&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAll&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAny&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Result&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Wait&lt;/code&gt;은 블로킹 &amp;mdash; Unity 메인 스레드 절대 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;Unity 핫패스 주의&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;매 프레임 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;은 GC 스파이크 &amp;mdash; Update 안에서 피하거나 UniTask 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;체크리스트&lt;/h3&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] 여러 작업이 서로 의존하지 않으면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.WhenAll(...)&lt;/code&gt;로 묶었는가?&lt;/li&gt;
&lt;li&gt;[ ] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;[]&lt;/code&gt; 결과 배열이 입력 순서를 따른다는 점을 인지하고 있는가?&lt;/li&gt;
&lt;li&gt;[ ] 타임아웃이 필요하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny + Delay&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAsync&lt;/code&gt;를 사용하고 있는가?&lt;/li&gt;
&lt;li&gt;[ ] 여러 Task가 동시에 실패할 가능성이 있으면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 변수를 살려 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception.InnerExceptions&lt;/code&gt;를 순회하는가?&lt;/li&gt;
&lt;li&gt;[ ] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt; 이후 나머지 Task를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;CancellationToken&lt;/code&gt;으로 정리하고 있는가?&lt;/li&gt;
&lt;li&gt;[ ] Unity UI/Update 코드에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Wait()&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WaitAll&lt;/code&gt;을 사용하지 않는가?&lt;/li&gt;
&lt;li&gt;[ ] Update 안에서 매 프레임 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;을 호출하고 있지는 않은가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAll&lt;/code&gt;과 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;WhenAny&lt;/code&gt;는 비동기 코드의 &quot;그리고/또는&quot;입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 한 번에 작업 한 개씩만 기다리던 한계를 넘어, 여러 비동기 작업을 자연스럽게 조합할 수 있게 해주는 가장 기본적인 도구입니다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>C#</category>
      <category>기초</category>
      <category>입문</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/578</guid>
      <comments>https://everyday-devup.tistory.com/578#entry578comment</comments>
      <pubDate>Sat, 9 May 2026 00:00:11 +0900</pubDate>
    </item>
    <item>
      <title>[PART13.비동기와 스레딩 기초(7/15)] 비동기 메서드의 반환형 &amp;mdash; Task vs Task&amp;lt;T&amp;gt; vs void</title>
      <link>https://everyday-devup.tistory.com/577</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(7/15)] 비동기 메서드의 반환형 &amp;mdash; Task vs Task&amp;amp;lt;T&amp;amp;gt; vs void&lt;/h1&gt;
&lt;p style=&quot;color: #868e96; font-size: 14px; margin: 0 0 32px; line-height: 1.6;&quot; data-ke-size=&quot;size16&quot;&gt;결과가 없으면 Task / 있으면 Task&amp;amp;lt;T&amp;amp;gt; / async void는 이벤트 핸들러 외에는 절대 금지&lt;/p&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;문제 제기 &amp;mdash; &quot;버튼을 누르면 게임이 통째로 죽는다&quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;개념 정의 &amp;mdash; 세 반환형의 의미&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;내부 동작 &amp;mdash; 컴파일러가 만드는 세 가지 빌더&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;실전 적용 &amp;mdash; 반환형 선택 규칙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;함정과 주의사항&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;C# 버전별 변화&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;정리&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 제기 &amp;mdash; &quot;버튼을 누르면 게임이 통째로 죽는다&quot;&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity에서 데이터를 비동기로 불러오는 메서드를 처음 작성한 신입 개발자는 보통 이런 코드를 짭니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class DataLoader : MonoBehaviour
{
    public async void LoadProfileAsync()  // 무심코 void 선택
    {
        var profile = await FetchFromServer();
        if (profile == null)
            throw new InvalidOperationException(&quot;프로필이 없습니다&quot;);
        ApplyProfile(profile);
    }
}

// 호출부 &amp;mdash; 버튼 클릭 핸들러
public void OnButtonClick()
{
    try
    {
        loader.LoadProfileAsync();  // 예외가 뜨면 잡힐까?
    }
    catch (Exception e)
    {
        Debug.LogError(e.Message);  // 절대 실행되지 않습니다
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;FetchFromServer()&lt;/code&gt; 가 null을 반환하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;LoadProfileAsync()&lt;/code&gt; 안에서 예외가 발생합니다. 그런데 호출부의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt; 는 이 예외를 &lt;b&gt;잡지 못합니다.&lt;/b&gt; Unity 에디터에서는 콘솔에 빨간 로그가 찍히고 끝나지만, IL2CPP 로 빌드한 모바일 빌드에서는 운이 나쁘면 &lt;b&gt;앱이 그대로 종료됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;같은 메서드를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 대신 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt; 로만 바꿔도 이 사고는 완전히 사라집니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async Task LoadProfileAsync()  // void &amp;rarr; Task
{
    var profile = await FetchFromServer();
    if (profile == null)
        throw new InvalidOperationException(&quot;프로필이 없습니다&quot;);
    ApplyProfile(profile);
}

// 호출부
public async void OnButtonClick()  // 이벤트 핸들러는 어쩔 수 없이 void
{
    try
    {
        await loader.LoadProfileAsync();  // await 가 예외를 다시 던져준다
    }
    catch (Exception e)
    {
        Debug.LogError(e.Message);  // 정상적으로 잡힌다
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;차이는 단 한 글자(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt; &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;)지만, 결과는 &lt;b&gt;앱 강제 종료&lt;/b&gt; 와 &lt;b&gt;정상 에러 로깅&lt;/b&gt; 만큼 다릅니다. 이 글은 왜 이런 차이가 생기는지, 컴파일러가 세 반환형을 IL 레벨에서 어떻게 다르게 처리하는지, 그리고 Unity 모바일 게임에서 어떤 규칙으로 선택해야 하는지 설명합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;2. 개념 정의 &amp;mdash; 세 반환형의 의미&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2.1 비유 &amp;mdash; 택배 송장과 영수증&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 메서드의 반환형은 호출자에게 돌려주는 &lt;b&gt;&quot;증빙서류&quot;&lt;/b&gt; 라고 생각하면 쉽습니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;반환형&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;비유&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;호출자가 할 수 있는 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;상품 교환권&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;나중에 결과 상품을 받음 + 도착 여부 확인 + 분실 시 환불 청구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;택배 송장 번호&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;도착 여부 확인 + 분실 시 환불 청구 (상품은 없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;아무것도 받지 못함&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;보냈는지조차 알 수 없음 &amp;mdash; 사고 나도 모름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt; 가 위험한 이유가 한눈에 보입니다. &lt;b&gt;호출자가 작업의 운명을 추적할 방법이 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2.2 SVG로 보는 세 반환형 비교&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/w0pa8/dJMcagen8Be/AAAAAAAAAAAAAAAAAAAAAPN-sTCbBST5MamCJIspV6i-zgastAzg0xoqwp3nxgjB/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=moz4D%2FUdYCll3sRjS4e3wv1m5NY%3D&quot; alt=&quot;async 메서드의 반환형 비교&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2.3 기본 코드 &amp;mdash; 세 반환형의 가장 단순한 형태&lt;/h3&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; &amp;mdash; 비동기 메서드 표시 키워드 (asynchronous)&lt;/b&gt; 메서드 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 를 사용할 수 있게 해주는 키워드. 컴파일러는 이 키워드가 붙은 메서드를 상태 머신(state machine) 으로 변환한다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;public async Task DoAsync() { await Task.Delay(100); }&lt;/code&gt; Task.Delay 가 끝날 때까지 메서드를 일시 중단했다가 재개&lt;/blockquote&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class AsyncReturnTypes
{
    // (1) 결과가 있는 비동기 &amp;mdash; Task&amp;lt;T&amp;gt;
    public async Task&amp;lt;int&amp;gt; GetUserScoreAsync(int userId)
    {
        await Task.Delay(100);   // 비동기 작업 (네트워크 호출 가정)
        return 9999;             // T(int) 결과 반환
    }

    // (2) 결과가 없는 비동기 &amp;mdash; Task
    public async Task SaveUserScoreAsync(int userId, int score)
    {
        await Task.Delay(100);   // 비동기 작업
        // return 문 없음
    }

    // (3) 이벤트 핸들러용 &amp;mdash; async void
    public async void OnSaveButtonClicked(object sender, EventArgs e)
    {
        try
        {
            await SaveUserScoreAsync(1, 9999);
        }
        catch (Exception ex)
        {
            // 이벤트 핸들러 안에서는 반드시 try-catch
            Debug.LogError(ex);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;세 메서드의 본문 로직은 비슷하지만 &lt;b&gt;반환형만 다릅니다.&lt;/b&gt; 이 차이가 호출자에게 무엇을 줄 수 있는지를 결정합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 는 &lt;b&gt;호출자에게 객체를 돌려주기 때문에&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 로 결과를 받고 예외를 잡고 완료 시점을 알 수 있습니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 는 돌려줄 객체가 아예 없습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;3. 내부 동작 &amp;mdash; 컴파일러가 만드는 세 가지 빌더&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3.1 상태 머신과 메서드 빌더&lt;/h3&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;상태 머신 (state machine)&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드는 컴파일 시점에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncStateMachine&lt;/code&gt; 인터페이스를 구현하는 별도의 구조체로 변환된다. 이 구조체에는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;&amp;lt;&amp;gt;1__state&lt;/code&gt; 필드가 있어서 메서드가 어디까지 실행됐는지 단계를 기록한다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;메서드 빌더 (method builder)&lt;/b&gt; 상태 머신과 호출자 사이를 잇는 어댑터. 반환형(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt;)에 따라 세 종류 중 하나가 자동으로 선택된다. 결과를 호출자에게 전달하고, 예외가 발생하면 적절히 라우팅한다.&lt;/blockquote&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/cMPPIP/dJMcahRRMf2/AAAAAAAAAAAAAAAAAAAAALOGGqhyD6q3j38_5b_pBrOg_ffhLkBDK9UXho1WNN5M/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=e%2BfMT4MxU9Hs8ZDqk83D%2Fs8F1A4%3D&quot; alt=&quot;컴파일러가 생성하는 상태 머신과 빌더&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3.2 IL로 보는 세 빌더 &amp;mdash; 결정적 차이&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;세 가지 반환형을 가진 메서드를 컴파일해서 IL을 비교해보면, 컴파일러가 &lt;b&gt;빌더 타입만 다르고 나머지 구조는 똑같다는&lt;/b&gt; 사실이 드러납니다.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;Task&amp;lt;T&amp;gt; 버전 IL &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&amp;lt;int&amp;gt;&lt;/code&gt;&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async Task&amp;lt;int&amp;gt; GetValueAsync()
{
    await Task.Delay(10);
    return 42;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 상태 머신 구조체 필드 정의
.field public valuetype System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1&amp;lt;int32&amp;gt;
    '&amp;lt;&amp;gt;t__builder'

// MoveNext() 정상 종료 &amp;mdash; 결과를 Task&amp;lt;int&amp;gt; 에 저장
IL_008a: call instance void
    AsyncTaskMethodBuilder`1&amp;lt;int32&amp;gt;::SetResult(!0)   // 42를 Task에 박음

// MoveNext() catch 블록 &amp;mdash; 예외를 Task&amp;lt;int&amp;gt; 에 저장
IL_0075: call instance void
    AsyncTaskMethodBuilder`1&amp;lt;int32&amp;gt;::SetException(class System.Exception)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;Task 버전 IL &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&lt;/code&gt; (제네릭 아님)&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async Task DoWorkAsync()
{
    await Task.Delay(10);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 상태 머신 구조체 필드 정의
.field public valuetype System.Runtime.CompilerServices.AsyncTaskMethodBuilder
    '&amp;lt;&amp;gt;t__builder'

// MoveNext() 정상 종료 &amp;mdash; 결과 없이 Task 만 완료
IL_008a: call instance void
    AsyncTaskMethodBuilder::SetResult()              // 인자 없음

// MoveNext() catch 블록 &amp;mdash; 예외를 Task 에 저장
IL_0075: call instance void
    AsyncTaskMethodBuilder::SetException(class System.Exception)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;void 버전 IL &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncVoidMethodBuilder&lt;/code&gt; (위험)&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public async void FireAndForgetAsync()
{
    await Task.Delay(10);
    throw new InvalidOperationException(&quot;async void에서 발생한 예외&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;gradle&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 상태 머신 구조체 필드 정의 &amp;mdash; 빌더 타입만 다르다
.field public valuetype System.Runtime.CompilerServices.AsyncVoidMethodBuilder
    '&amp;lt;&amp;gt;t__builder'

// MoveNext() catch 블록 &amp;mdash; Task 가 없으므로 SynchronizationContext 로 직송
IL_007e: call instance void
    AsyncVoidMethodBuilder::SetException(class System.Exception)
//  &amp;uarr; 동일한 메서드 이름이지만, 내부 구현이 완전히 다르다.
//  AsyncTaskMethodBuilder.SetException &amp;rarr; Task.Exception 에 저장
//  AsyncVoidMethodBuilder.SetException &amp;rarr; SynchronizationContext.Post(throw)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3.3 IL 해설 &amp;mdash; 같은 호출, 다른 운명&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;세 IL 모두 마지막에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SetException(e)&lt;/code&gt; 를 호출합니다. &lt;b&gt;메서드 이름은 같지만 동작이 완전히 다릅니다.&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;빌더&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SetException(e)&lt;/code&gt; 가 하는 일&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;반환된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Exception&lt;/code&gt; 속성에 예외를 저장. 호출자가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 하면 그 시점에 예외가 다시 던져진다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;반환된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 에 예외 저장. 위와 동일.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncVoidMethodBuilder&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;반환할 Task 가 없다.&lt;/b&gt; 메서드 시작 시 캡처해둔 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt; 의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Post()&lt;/code&gt; 를 호출해 예외를 그 컨텍스트 위에서 다시 던진다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt; 가 없는 환경(콘솔 앱, 일반 워커 스레드, 일부 Unity 컨텍스트)에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ThreadPool&lt;/code&gt; 위에서 예외가 던져지고, 이는 &lt;b&gt;잡히지 않는 예외(unhandled exception)&lt;/b&gt; 로 취급되어 프로세스를 그대로 종료시킵니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심: &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 는 예외를 호출자가 잡을 수 있는 경로가 IL 레벨에서부터 존재하지 않습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;4. 실전 적용 &amp;mdash; 반환형 선택 규칙&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.1 결정 트리&lt;/h3&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/bczYxW/dJMcadWdD7k/AAAAAAAAAAAAAAAAAAAAAGx3gr2oAuqRKUkz7CrCRqs7TA800kYomp6LKhTW772p/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=ajWygmfOnwg99MrMu0n9HQjeybg%3D&quot; alt=&quot;반환형 선택 결정 트리&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.2 Before/After &amp;mdash; Unity 모바일 게임 시나리오&lt;/h3&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;시나리오 1: 게임 데이터 저장 메서드 &amp;mdash; 결과 없음&lt;/h4&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;❌ Before &amp;mdash; async void 로 작성&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class SaveSystem : MonoBehaviour
{
    // 잘못된 패턴 &amp;mdash; 라이브러리성 메서드를 async void 로 노출
    public async void SaveGameAsync(GameData data)
    {
        var json = JsonUtility.ToJson(data);
        await File.WriteAllTextAsync(savePath, json);
        // 실패 시 예외가 어디로 가는지 호출자가 알 수 없음
    }
}

// 호출부
public class GameManager : MonoBehaviour
{
    private SaveSystem saveSystem;

    public async Task OnLevelClearAsync()
    {
        try
        {
            saveSystem.SaveGameAsync(currentData);   // 즉시 반환 (Task 가 없어 await 불가)
            await ShowVictoryAnimationAsync();        // 저장 완료 전에 진행
            // 저장이 끝났는지 호출자가 확인할 방법이 없다
        }
        catch (IOException e)
        {
            Debug.LogError(&quot;저장 실패&quot;);  // 잡히지 않는다
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;문제 두 가지: (1) 호출자가 저장 완료를 기다릴 수 없어 저장 도중 다른 화면으로 넘어가면 데이터 일부가 손상될 수 있고, (2) &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IOException&lt;/code&gt; 이 발생하면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt; 를 무시하고 프로세스가 종료됩니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;✅ After &amp;mdash; async Task 로 변경&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class SaveSystem : MonoBehaviour
{
    // 올바른 패턴 &amp;mdash; 호출자에게 Task 를 돌려준다
    public async Task SaveGameAsync(GameData data)
    {
        var json = JsonUtility.ToJson(data);
        await File.WriteAllTextAsync(savePath, json);
    }
}

public class GameManager : MonoBehaviour
{
    public async Task OnLevelClearAsync()
    {
        try
        {
            await saveSystem.SaveGameAsync(currentData);  // 완료까지 대기
            await ShowVictoryAnimationAsync();             // 저장 후 애니메이션
        }
        catch (IOException e)
        {
            Debug.LogError($&quot;저장 실패: {e.Message}&quot;);    // 정상적으로 잡힌다
            ShowSaveFailedDialog();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt; 로 바꾸면 IL 레벨에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&lt;/code&gt; 가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 를 반환하므로 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 가 예외를 호출자에게 다시 던질 수 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;font-size: 15px; font-weight: bold; color: #495057; margin: 24px 0 10px; padding-left: 10px; border-left: 3px solid #adb5bd;&quot; data-ke-size=&quot;size20&quot;&gt;시나리오 2: 사용자 점수 조회 &amp;mdash; 결과 있음&lt;/h4&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ 결과를 멤버 변수로 흘려보내는 패턴 (async void 강요)
public class ScoreLoader : MonoBehaviour
{
    public int LoadedScore;  // 외부 상태에 의존

    public async void LoadScoreAsync(int userId)
    {
        LoadedScore = await FetchFromServer(userId);
        // 호출자는 LoadedScore 가 갱신됐는지 어떻게 알지?
    }
}

// 호출부 &amp;mdash; 폴링 + 타임아웃 같은 임시방편 코드 양산
while (scoreLoader.LoadedScore == 0) await Task.Yield();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✅ Task&amp;lt;T&amp;gt; 로 결과를 직접 돌려준다
public class ScoreLoader : MonoBehaviour
{
    public async Task&amp;lt;int&amp;gt; LoadScoreAsync(int userId)
    {
        return await FetchFromServer(userId);  // 결과를 Task&amp;lt;int&amp;gt; 에 담아 반환
    }
}

// 호출부 &amp;mdash; 한 줄
int score = await scoreLoader.LoadScoreAsync(userId);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;판단 기준&lt;/b&gt;: 메서드가 값을 만들어낸다면 그것을 멤버 변수가 아니라 반환값으로 흘려보내야 합니다. 그러려면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 가 강제됩니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4.3 IL 비교 &amp;mdash; async void &amp;rarr; async Task 변경의 효과&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SaveGameAsync&lt;/code&gt; 의 빌더 필드 타입만 비교하면 됩니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;actionscript&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ async void
.field public valuetype AsyncVoidMethodBuilder '&amp;lt;&amp;gt;t__builder'

// ✅ async Task
.field public valuetype AsyncTaskMethodBuilder '&amp;lt;&amp;gt;t__builder'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;소스 코드에서는 키워드 한 글자만 바뀌었지만, IL 에서는 호출자에게 돌려줄 객체의 종류가 통째로 달라집니다. 이 한 글자가 &lt;b&gt;예외를 잡을 수 있느냐 없느냐&lt;/b&gt; 를 결정합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;5. 함정과 주의사항&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.1 ❌ 함정 1: 라이브러리 메서드를 async void 로 노출&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ 절대 금지 &amp;mdash; 라이브러리(공용 모듈) 메서드는 async void 금지
public class NetworkClient
{
    public async void SendRequestAsync(string url)  // 사용자가 await 못 함
    {
        var response = await httpClient.GetAsync(url);
        ProcessResponse(response);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✅ 라이브러리 메서드는 항상 Task / Task&amp;lt;T&amp;gt;
public class NetworkClient
{
    public async Task&amp;lt;HttpResponseMessage&amp;gt; SendRequestAsync(string url)
    {
        var response = await httpClient.GetAsync(url);
        return response;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원칙: 호출자(라이브러리 사용자)에게 비동기 작업의 제어권을 넘겨준다.&lt;/b&gt; 호출자는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.WhenAll&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt; 중 무엇이든 자기 사정에 맞게 선택할 수 있어야 합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 는 이 모든 선택지를 박탈합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.2 ❌ 함정 2: 이벤트 핸들러에서 try-catch 누락&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ 위험 &amp;mdash; 예외가 새어 나가면 앱이 죽는다
public class UIController : MonoBehaviour
{
    public async void OnLoadButtonClicked()
    {
        var data = await LoadAsync();  // 예외 발생 가능
        UpdateUI(data);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✅ 이벤트 핸들러는 메서드 전체를 try-catch 로 감싼다
public class UIController : MonoBehaviour
{
    public async void OnLoadButtonClicked()
    {
        try
        {
            var data = await LoadAsync();
            UpdateUI(data);
        }
        catch (Exception e)
        {
            Debug.LogException(e);
            ShowErrorDialog(e.Message);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 는 예외를 잡을 외부 경로가 없으므로 메서드 자체에서 모든 예외를 흡수해야 합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.3 ❌ 함정 3: 단위 테스트가 통과해도 동작 검증이 안 됨&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ async void 메서드는 테스트가 거짓 통과한다
[Test]
public void SaveTest_async_void()
{
    var system = new SaveSystem();
    system.SaveGameAsync_void(testData);   // async void
    // 테스트 러너는 이 줄을 지나면 즉시 다음으로 진행
    // SaveGameAsync 가 끝나기 전에 테스트가 &quot;성공&quot; 으로 종료
    Assert.IsTrue(File.Exists(savePath));  // 파일이 아직 없을 수 있음
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✅ async Task 면 테스트 러너가 완료까지 대기한다
[Test]
public async Task SaveTest_async_Task()
{
    var system = new SaveSystem();
    await system.SaveGameAsync(testData);  // 완료까지 대기
    Assert.IsTrue(File.Exists(savePath));   // 파일이 반드시 존재
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;테스트가 거짓 통과(false positive)한다는 사실 자체가 위험합니다. 통과한 테스트가 실제로 동작을 보장하지 않기 때문입니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.4 ❌ 함정 4: Unity 라이프사이클에서의 async void&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ⚠️ 주의 &amp;mdash; Start() 같은 단발성 라이프사이클 메서드만 허용. Update() 는 금지
public class BadUsage : MonoBehaviour
{
    private async void Update()  // ❌ 매 프레임 호출 + 비동기 = 폭주
    {
        await LoadDataAsync();   // 60 FPS 면 초당 60번 새로운 비동기 작업 시작
        UpdateUI();
    }

    private async void Start()   // ✅ 한 번만 호출되므로 OK (이벤트 핸들러 성격)
    {
        try
        {
            await InitializeAsync();
        }
        catch (Exception e)
        {
            Debug.LogException(e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Start()&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OnEnable()&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OnDisable()&lt;/code&gt; 처럼 한 번만 호출되는 라이프사이클 메서드는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 가 가능하지만, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Update()&lt;/code&gt; 는 매 프레임 비동기 작업이 누적되므로 절대 사용하지 않습니다. 이 경우 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Coroutine&lt;/code&gt; 이나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask.Forget()&lt;/code&gt; 패턴을 사용합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5.5 Unity 실전 추가 함정 &amp;mdash; destroyCancellationToken 미연결&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ GameObject 가 파괴된 뒤에 await 가 끝나면 MissingReferenceException
public async Task LoadAndApplyAsync()
{
    var data = await FetchAsync();         // 5초 걸림
    // 그동안 사용자가 씬을 바꿔서 이 GameObject 가 파괴됨
    transform.position = data.spawnPoint;  //   MissingReferenceException
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✅ destroyCancellationToken (Unity 2022.2+) 으로 안전하게 취소
public async Task LoadAndApplyAsync()
{
    var data = await FetchAsync(destroyCancellationToken);
    transform.position = data.spawnPoint;  // 파괴되면 여기 도달하지 않음
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 함정은 반환형과 직접 관련되진 않지만, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt; 를 쓰기 시작하면 반드시 동반되는 패턴이라 함께 익혀둡니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;6. C# 버전별 변화&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.1 C# 5.0 (2012) &amp;mdash; async/await 도입과 세 반환형&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 5.0 이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 와 함께 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt; 반환형을 모두 도입했습니다. 이 시점부터 컴파일러는 메서드 빌더 세 종류(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncVoidMethodBuilder&lt;/code&gt;) 를 자동 선택했습니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.2 C# 7.0 (2017) &amp;mdash; ValueTask / ValueTask&amp;lt;T&amp;gt; 추가&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// C# 7.0+ &amp;mdash; 결과가 동기적으로 즉시 준비되는 경우 GC 할당 회피
public async ValueTask&amp;lt;int&amp;gt; GetCachedScoreAsync(int userId)
{
    if (cache.TryGetValue(userId, out var score))
        return score;  // Task 객체 할당 없이 즉시 반환

    return await FetchFromServer(userId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 는 참조 타입(class) 이라 매번 힙에 할당되지만, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; 는 구조체(struct) 라 동기 완료 경로에서 할당이 0입니다. &lt;b&gt;자주 호출되는 핫패스에서 결과가 캐시될 가능성이 높을 때만 도입&lt;/b&gt;합니다(잘못 쓰면 더 느려질 수 있음).&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.3 C# 7.0 &amp;mdash; Custom Async Method Builder&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// AsyncMethodBuilderAttribute 로 사용자 정의 빌더 지정 가능
[AsyncMethodBuilder(typeof(MyTaskMethodBuilder))]
public struct MyTask { /* ... */ }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 기능 덕분에 Unity 생태계의 &lt;b&gt;UniTask&lt;/b&gt; 가 등장했습니다. UniTask 는 자체 빌더를 사용해 GC 할당 없는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async UniTask&lt;/code&gt; 와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async UniTask&amp;lt;T&amp;gt;&lt;/code&gt; 를 제공합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;6.4 UniTask &amp;mdash; Unity 모바일 실전 표준&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ✅ Unity 모바일 게임에서 사실상의 표준
public class GameLoader : MonoBehaviour
{
    public async UniTask&amp;lt;UserData&amp;gt; LoadUserAsync()  // Task&amp;lt;T&amp;gt; 대신
    {
        var data = await UnityWebRequest.Get(url).SendWebRequest();
        return Parse(data);
    }

    // async UniTaskVoid: async void 의 안전한 대체재
    public async UniTaskVoid OnButtonClicked()
    {
        try
        {
            var user = await LoadUserAsync();
            ApplyUI(user);
        }
        catch (Exception e)
        {
            Debug.LogException(e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask&amp;lt;T&amp;gt;&lt;/code&gt; 는 구조체 기반이라 0 GC 할당이고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTaskVoid&lt;/code&gt; 는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 의 예외 라우팅 문제를 해결합니다. Unity 모바일에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 보다 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask&amp;lt;T&amp;gt;&lt;/code&gt; 를 사용하는 것이 권장됩니다(추정 &amp;mdash; 프로젝트마다 도입 여부 판단 필요).&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;7. 정리&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;세 반환형의 본질은 &lt;b&gt;호출자에게 무엇을 돌려줄 것인가&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;반환형&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;언제 쓰는가&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;호출자 능력&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;IL 빌더&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;결과가 있는 비동기 메서드 (대부분의 경우)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;결과 받음 + 완료 대기 + 예외 catch&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;결과가 없는 비동기 메서드 (저장&amp;middot;전송 등)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;완료 대기 + 예외 catch&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;이벤트 핸들러 전용&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;아무것도 못 함&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncVoidMethodBuilder&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;기억해야 할 5가지&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;결과가 있으면&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;b&gt;없으면&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;. 일반 메서드의 기본 선택지는 이 둘 중 하나다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 는 이벤트 핸들러 전용.&lt;/b&gt; 라이브러리&amp;middot;공용 모듈&amp;middot;테스트 가능한 코드에서는 절대 쓰지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 의 예외는 호출자가 잡을 수 없다.&lt;/b&gt; IL 레벨에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncVoidMethodBuilder.SetException&lt;/code&gt; 이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt; 로 직송하기 때문이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 핸들러는 메서드 전체를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt; 로 감싼다.&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 의 유일한 안전망은 자기 자신뿐이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단위 테스트는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt; 에서만 신뢰할 수 있다.&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 는 테스트가 거짓 통과한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;font-size: 15px; font-weight: bold; color: #1a1a1a; margin: 20px 0 8px; padding: 6px 0; border-bottom: 1px dashed #dee2e6;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 실전 체크리스트&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] 일반 메서드의 반환형은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 인가?&lt;/li&gt;
&lt;li&gt;[ ] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OnButtonClicked&lt;/code&gt; 같은 이벤트 핸들러에만 있는가?&lt;/li&gt;
&lt;li&gt;[ ] 모든 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 메서드는 메서드 전체가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try-catch&lt;/code&gt; 로 감싸져 있는가?&lt;/li&gt;
&lt;li&gt;[ ] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Update()&lt;/code&gt; 에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 가 들어가지 않았는가?&lt;/li&gt;
&lt;li&gt;[ ] 모바일 핫패스라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 대신 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask&amp;lt;T&amp;gt;&lt;/code&gt; 도입을 검토했는가?&lt;/li&gt;
&lt;li&gt;[ ] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 후에도 GameObject 가 살아있다는 보장이 필요한 곳에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;destroyCancellationToken&lt;/code&gt; 을 연결했는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>C#</category>
      <category>기초</category>
      <category>입문</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/577</guid>
      <comments>https://everyday-devup.tistory.com/577#entry577comment</comments>
      <pubDate>Fri, 8 May 2026 23:49:44 +0900</pubDate>
    </item>
    <item>
      <title>[PART13.비동기와 스레딩 기초(6/15)] async / await &amp;mdash; 비동기 흐름을 동기처럼 쓰는 문법</title>
      <link>https://everyday-devup.tistory.com/576</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR',sans-serif; line-height: 1.8; color: #2d2d2d; max-width: 860px; margin: 0 auto; word-break: keep-all;&quot;&gt;
&lt;h1 style=&quot;font-size: 28px; font-weight: 800; color: #1a1a1a; margin: 0 0 12px; line-height: 1.4;&quot;&gt;[PART13.비동기와 스레딩 기초(6/15)] async / await &amp;mdash; 비동기 흐름을 동기처럼 쓰는 문법&lt;/h1&gt;
&lt;p style=&quot;color: #868e96; font-size: 14px; margin: 0 0 32px; line-height: 1.6;&quot; data-ke-size=&quot;size16&quot;&gt;콜백 지옥을 동기 코드처럼 / 반환형 5종 / I/O는 await, CPU는 Task.Run / DoAsync 보면 무조건 그쪽&lt;/p&gt;
&lt;!-- TOC --&gt;
&lt;div style=&quot;background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px 24px; margin-bottom: 36px;&quot;&gt;
&lt;p style=&quot;font-size: 14px; font-weight: bold; color: #495057; margin: 0 0 8px;&quot; data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ol style=&quot;color: #495057; font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-1&quot;&gt;[문제 제기] &amp;mdash; 비동기 작업이 늘어나면 코드가 콜백 미로가 된다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-2&quot;&gt;[개념 정의] &amp;mdash; async 는 &quot;이 메서드 안에서 await 를 쓰겠다&quot;는 표시, await 는 &quot;제어권 양보 + 콜백 등록&quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-3&quot;&gt;[내부 동작] &amp;mdash; 반환형 5종과 그들이 awaitable 인 이유&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-4&quot;&gt;[실전 적용] &amp;mdash; I/O 는 await, CPU 는 await Task.Run, DoAsync 가 보이면 동기 버전 금지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-5&quot;&gt;[함정과 주의사항] &amp;mdash; .Result/.Wait, fire-and-forget, async void 남용&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-6&quot;&gt;[C# 버전별 변화] &amp;mdash; 시작은 5.0, 점차 확장&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #495057; text-decoration: none;&quot; href=&quot;#section-7&quot;&gt;[정리] &amp;mdash; 핵심 체크리스트&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 1 ===================== --&gt;
&lt;h2 id=&quot;section-1&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;1. [문제 제기] &amp;mdash; 비동기 작업이 늘어나면 코드가 콜백 미로가 된다&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity에서 모바일 게임을 만들다 보면 비동기로 처리해야 하는 일이 끊이지 않습니다. 서버에서 유저 정보를 받아오고, 그 정보로 인벤토리를 조회하고, 아이콘 텍스처를 다운로드하고, 마지막으로 캐시에 저장하는 흐름은 흔합니다. 이 모든 단계가 네트워크를 타기 때문에 메인 스레드를 막을 수는 없습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt;/&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 가 없던 시절에는 콜백으로 이 흐름을 표현했습니다. 결과를 받는 함수가 또 다른 비동기 호출을 시작하고, 그 결과를 받는 함수가 다시 다른 비동기 호출을 시작하는 식이었습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// async/await 없이 콜백으로 비동기 흐름을 잇는 옛날 패턴
public void LoadUserFlow(int userId, Action&amp;lt;UserProfile&amp;gt; onDone)
{
    api.GetUser(userId, user =&amp;gt;
    {
        api.GetInventory(user.Id, inventory =&amp;gt;
        {
            api.DownloadIcon(inventory.IconUrl, icon =&amp;gt;
            {
                cache.Save(icon, () =&amp;gt;
                {
                    onDone(new UserProfile(user, inventory, icon));
                });
            });
        });
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;콜백이 4단계만 쌓여도 들여쓰기가 화면 오른쪽으로 흘러갑니다. 예외가 발생하면 어디서 터졌는지 추적하기 어렵고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/catch&lt;/code&gt; 한 번으로 전체 흐름을 감쌀 수도 없습니다. 함수가 어디서 끝나는지도 한눈에 보이지 않습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;C# 5.0에 도입된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 는 이 문제를 정면으로 해결합니다. 비동기 작업을 마치 동기 코드처럼 위에서 아래로 한 줄씩 읽히게 만들어 주는 문법입니다. 컴파일러가 뒤에서 상태 머신(State Machine, 메서드를 여러 조각으로 쪼개고 어디까지 실행했는지 기억하는 자동 생성 자료구조)을 만들어 주기 때문에, 우리는 그 결과만 누리면 됩니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;본 글은 사용 문법&amp;middot;반환형 규칙&amp;middot;실전 패턴에 집중합니다. 컴파일러가 만드는 상태 머신의 내부 구조와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt; 의 동작 원리는 이미 앞 주제(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;05. Task와 Task&amp;lt;T&amp;gt;&lt;/code&gt;)와 별도 심화 글에서 다룹니다.&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 2 ===================== --&gt;
&lt;h2 id=&quot;section-2&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;2. [개념 정의] &amp;mdash; async 는 &quot;이 메서드 안에서 await 를 쓰겠다&quot;는 표시, await 는 &quot;제어권 양보 + 콜백 등록&quot;&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2-1. 비유: 우체국 등기와 알림 문자&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 를 직관적으로 이해하려면 우체국 등기 신청을 떠올리면 좋습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; &amp;mdash; 비동기 메서드 한정자 (asynchronous modifier)&lt;/b&gt; 메서드&amp;middot;람다&amp;middot;로컬 함수 앞에 붙어 &quot;이 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 를 사용하겠다&quot;는 신호를 컴파일러에게 전달합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 자체가 메서드를 백그라운드에서 실행시키는 것은 아닙니다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;public async Task&amp;lt;string&amp;gt; GetAsync() { ... }&lt;/code&gt; 컴파일러가 이 메서드를 상태 머신으로 변환합니다.&lt;/blockquote&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; &amp;mdash; 비동기 대기 연산자 (await operator)&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; &amp;middot; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; &amp;middot; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt; &amp;middot; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; 같은 awaitable 객체 앞에 붙어 &quot;작업이 끝날 때까지 이 메서드의 실행을 멈추되, 호출자에게 제어권을 즉시 돌려준다&quot;를 의미합니다. 작업이 끝나면 멈췄던 그 줄 다음부터 자동으로 이어 실행됩니다.&lt;br style=&quot;margin-bottom: 8px;&quot; /&gt;&lt;b&gt;예시:&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;var html = await client.GetStringAsync(url);&lt;/code&gt; HTTP 응답이 도착할 때까지 스레드를 점유하지 않습니다.&lt;/blockquote&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/oxuY0/dJMcaaFgdOF/AAAAAAAAAAAAAAAAAAAAAPvoXsTMXNV0q7Uzh_FL2PUlY4Xkh6NJyli0or9_Z_LU/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=zE%2Fk5YJKvq4AJEaRFvCaQXhiL9I%3D&quot; alt=&quot;async/await &amp;asymp; 등기 + 알림 문자&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 단어가 주는 인상에 속지 않는 것입니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 는 &quot;기다린다&quot;가 아니라 &quot;&lt;b&gt;기다릴 일을 예약하고 즉시 자리를 비운다&lt;/b&gt;&quot; 입니다. 작업이 끝나면 시스템이 알아서 우리를 다시 호출해 줍니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2-2. 가장 기본적인 사용 형태&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;문제 정의에서 인용한 사양 그대로의 형태가 가장 단순한 예입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.Net.Http;
using System.Threading.Tasks;

public class HtmlFetcher
{
    private static readonly HttpClient client = new();

    // async 한정자, Task&amp;lt;string&amp;gt; 반환형, 메서드 이름 끝에 Async
    public async Task&amp;lt;string&amp;gt; GetAsync(string url)
    {
        // await 는 Task&amp;lt;string&amp;gt; 앞에만 사용 가능
        var html = await client.GetStringAsync(url);
        return html.Trim();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;세 줄짜리 메서드지만 약속이 빼곡합니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 한정자 &amp;mdash; &quot;이 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 를 쓰겠다&quot;는 컴파일러 신호&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;string&amp;gt;&lt;/code&gt; 반환형 &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 가능한 awaitable. 메서드 본문은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;string&lt;/code&gt; 을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;return&lt;/code&gt; 하지만 컴파일러가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;string&amp;gt;&lt;/code&gt; 으로 감싸 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Async&lt;/code&gt; 접미사 &amp;mdash; 호출자 입장에서 &quot;이건 비동기니까 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 붙여야 해&quot;를 즉시 알아챌 수 있는 명명 규약&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 가 끝나면 그 결과(여기서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;string&lt;/code&gt;)를 꺼내 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;html&lt;/code&gt; 에 대입. 끝나기 전이면 호출자에게 제어권을 돌려줍니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;2-3. IL 레벨에서 본 변환 결과&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이 메서드를 컴파일하고 IL을 확인하면 우리가 작성한 한 줄짜리 메서드가 사라지고, 컴파일러가 만든 자동 생성 코드가 그 자리를 대체했음을 볼 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 호출자 진입점 &amp;mdash; 우리가 작성한 GetAsync() 의 실제 IL
.method public hidebysig instance class Task`1&amp;lt;string&amp;gt; GetAsync(string url) cil managed
{
    .custom instance void AsyncStateMachineAttribute::.ctor(class Type) // 컴파일러가 붙인 어트리뷰트
    .locals init ([0] valuetype Sample1/'&amp;lt;GetAsync&amp;gt;d__1') // 상태 머신 구조체

    IL_0000: ldloca.s 0
    IL_0002: call AsyncTaskMethodBuilder`1&amp;lt;string&amp;gt;::Create() // 빌더 생성
    IL_0007: stfld ...t__builder
    IL_000c: ldloca.s 0
    IL_000e: ldarg.1
    IL_000f: stfld string ...url   // 인자를 상태 머신 필드에 저장
    IL_0014: ldloca.s 0
    IL_0016: ldc.i4.m1
    IL_0017: stfld int32 ...'&amp;lt;&amp;gt;1__state' // 초기 상태 -1
    IL_001c: ldloca.s 0
    IL_001e: ldflda ...t__builder
    IL_0023: ldloca.s 0
    IL_0025: call AsyncTaskMethodBuilder`1::Start&amp;lt;...&amp;gt;(!!0&amp;amp;) // 상태 머신 가동
    IL_002a: ldloca.s 0
    IL_002c: ldflda ...t__builder
    IL_0031: call get_Task() // 빌더에서 Task 꺼내서 호출자에게 반환
    IL_0036: ret
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// 우리가 적은 한 줄(`var html = await client.GetStringAsync(url);`)이
// 컴파일러가 만든 MoveNext 안에서 어떻게 펼쳐졌는지 핵심 부분만
IL_000a: ldsfld HttpClient Sample1::client
IL_0010: ldfld string ...url
IL_0015: callvirt Task`1&amp;lt;string&amp;gt; HttpClient::GetStringAsync(string)
IL_001a: callvirt TaskAwaiter`1::GetAwaiter()
IL_001f: stloc.2
IL_0022: call TaskAwaiter`1::get_IsCompleted() // 이미 끝났으면 그대로 진행
IL_0027: brtrue.s IL_0065
// 끝나지 않았으면: 상태 머신을 힙으로 박싱, 콜백 등록, 메서드 빠져나감
IL_0042: call AsyncTaskMethodBuilder`1::AwaitUnsafeOnCompleted&amp;lt;...&amp;gt;(...)
IL_0047: leave.s IL_009f
// &amp;darr; 작업 완료 시 시스템이 다시 들어와 IL_0049 부터 실행
IL_0065: ldloca.s 2
IL_0067: call TaskAwaiter`1::GetResult() // string 결과 꺼냄
IL_006c: callvirt String::Trim() // 우리가 적은 .Trim() 호출&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심만 짚으면 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 한정자가 붙은 메서드는 IL 레벨에서 본문이 통째로 비워지고, &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncStateMachine&lt;/code&gt;&lt;/b&gt; 구조체가 만들어져 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNext&lt;/code&gt; 라는 메서드 안으로 본문이 옮겨집니다. 호출자가 부르는 메서드는 이제 &quot;상태 머신을 만들고 시작 &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 를 반환&quot;만 합니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 는 IL 한 줄로 컴파일되지 않습니다. &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetAwaiter()&lt;/code&gt; &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCompleted&lt;/code&gt; 검사 &amp;rarr; (미완료면) 콜백 등록 후 메서드 종료, (완료면) &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetResult()&lt;/code&gt;&lt;/b&gt; 라는 패턴으로 펼쳐집니다.&lt;/li&gt;
&lt;li&gt;그래서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 가 &quot;기다림&quot;이 아니라 &quot;콜백 등록 후 자리 비움&quot;이라는 비유가 정확합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCompleted&lt;/code&gt; 가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;false&lt;/code&gt; 면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AwaitUnsafeOnCompleted&lt;/code&gt; 가 호출되고, 메서드는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;leave.s&lt;/code&gt; 로 빠져나갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;상태 머신의 모든 상태 전이와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNext&lt;/code&gt; 의 분기 구조는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;05. Task와 Task&amp;lt;T&amp;gt;&lt;/code&gt; 에서 이미 자세히 분석했습니다. 본 글은 &quot;이 메커니즘이 있다는 사실&quot;만 활용해 사용 문법에 집중합니다.&lt;/blockquote&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 3 ===================== --&gt;
&lt;h2 id=&quot;section-3&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;3. [내부 동작] &amp;mdash; 반환형 5종과 그들이 awaitable 인 이유&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드의 반환형에는 엄격한 제약이 있습니다. &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 로 결과를 받을 수 있는 타입(awaitable)&lt;/b&gt; 이거나, 이벤트 시스템이 요구하는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt; 여야 합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3-1. 반환형 5종 한 눈에&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;반환형&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;언제 쓰는가&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;await 가능&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;메모리&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;결과값 없는 비동기 작업. 동기의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt; 와 짝&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;참조 타입(힙)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;결과값이 있는 비동기 작업. 가장 흔함&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;참조 타입(힙)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;동기 완료 가능성이 매우 높은 결과값 없는 작업&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;값 타입(스택)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;동기 완료 가능성이 매우 높은 결과값 있는 작업&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;값 타입(스택)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;이벤트 핸들러 한정&lt;/b&gt;. 그 외에는 절대 금지&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;추적 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;margin: 20px 0 24px; text-align: center;&quot;&gt;&lt;img style=&quot;max-width: 100%; height: auto; border-radius: 6px;&quot; src=&quot;https://blog.kakaocdn.net/dna/m7PHH/dJMcagFtIN3/AAAAAAAAAAAAAAAAAAAAADMnxFVsRJM3KL4Oj5n4vwXTIY5tHEymSZ2qceH6hvy2/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Q9Dwi9KvGkyB%2FAGfjel4IlAc%2Bh8%3D&quot; alt=&quot;async 메서드의 반환형 결정 흐름&quot; /&gt;&lt;/div&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3-2. 코드로 보는 4종&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;using System.IO;
using System.Threading.Tasks;

public class ReturnTypeShowcase
{
    // 1. Task : 결과값이 없는 작업
    public async Task SaveAsync(string path, string text)
    {
        await File.WriteAllTextAsync(path, text);
        // return 키워드 자체를 안 쓰는 게 자연스럽다
    }

    // 2. Task&amp;lt;T&amp;gt; : 결과값이 있는 작업 &amp;mdash; 가장 흔한 형태
    public async Task&amp;lt;int&amp;gt; CountLinesAsync(string path)
    {
        var lines = await File.ReadAllLinesAsync(path);
        return lines.Length; // int 를 return 하면 컴파일러가 Task&amp;lt;int&amp;gt; 로 감싸 준다
    }

    // 3. ValueTask&amp;lt;T&amp;gt; : 동기 완료 가능성이 매우 높을 때 (캐시 적중 등)
    private string? cached;
    public ValueTask&amp;lt;string&amp;gt; ReadCachedAsync(string path)
    {
        if (cached is not null)
            return new ValueTask&amp;lt;string&amp;gt;(cached); // 동기 완료, 힙 할당 0
        return new ValueTask&amp;lt;string&amp;gt;(LoadAsync(path));
    }
    private async Task&amp;lt;string&amp;gt; LoadAsync(string path)
    {
        var text = await File.ReadAllTextAsync(path);
        cached = text;
        return text;
    }

    // 4. async void : 이벤트 핸들러에서만 &amp;mdash; 일반 메서드에서는 절대 금지
    // private async void OnButtonClick(object sender, EventArgs e) { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;각 반환형은 자신만의 빌더(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncTaskMethodBuilder&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncValueTaskMethodBuilder&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncValueTaskMethodBuilder&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AsyncVoidMethodBuilder&lt;/code&gt;)를 가지고 있고, 컴파일러는 반환형에 맞는 빌더로 상태 머신을 구동합니다. 우리가 신경 써야 할 것은 &quot;어떤 시그니처를 골라야 하는가&quot;뿐입니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; vs &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; 의 정확한 트레이드오프와 다회 await 금지&amp;middot;풀링 같은 디테일은 다음 주제(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;07. 비동기 메서드의 반환형 &amp;mdash; Task vs Task&amp;lt;T&amp;gt; vs void&lt;/code&gt;)에서 따로 다룹니다. 본 글에서는 &quot;기본은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;, 핫패스에서만 측정 후 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt; 도입&quot; 정도만 기억하면 충분합니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;3-3. async void 가 위험한 이유&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 가 일반 메서드에서 금지되는 이유는 두 가지입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class VoidDanger
{
    // 절대 따라하면 안 되는 패턴
    public async void DoBadlyAsync()
    {
        await Task.Delay(10);
        throw new InvalidOperationException(&quot;터졌어요&quot;); // 어디서도 잡을 수 없음
    }

    public async Task CallerAsync()
    {
        try
        {
            DoBadlyAsync(); // Task 가 없어 await 불가
            // 메서드가 끝났는지 알 길도 없음
        }
        catch (InvalidOperationException) // 여기로 안 들어옴
        {
            // 도달 불가
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;완료 추적 불가&lt;/b&gt; &amp;mdash; 반환형이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt; 니까 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 할 대상이 없습니다. 호출자는 그 비동기 메서드가 끝났는지, 성공했는지조차 알 수 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예외 처리 불가&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 안에서 발생한 예외는 호출자의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/catch&lt;/code&gt; 로 전파되지 않습니다. 대신 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt; 에 즉시 던져져 보통 프로세스가 강제 종료됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이벤트 핸들러(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;OnButtonClick&lt;/code&gt;, Unity 의 일부 UI 이벤트)는 이벤트 시그니처가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt; 를 강제하기 때문에 어쩔 수 없이 사용합니다. 그 외에는 무조건 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 또는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; 를 반환하도록 합니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 4 ===================== --&gt;
&lt;h2 id=&quot;section-4&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;4. [실전 적용] &amp;mdash; I/O 는 await, CPU 는 await Task.Run, DoAsync 가 보이면 동기 버전 금지&lt;/h2&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 코드를 처음 만나는 신입 개발자가 가장 먼저 부딪치는 결정은 &quot;어떤 호출 앞에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 를 붙일 것인가&quot; 입니다. 두 가지 큰 패턴만 익히면 됩니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4-1. I/O 바운드: 외부에 일을 맡기고 스레드를 풀어준다&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 요청&amp;middot;파일 입출력&amp;middot;DB 쿼리 같은 작업은 대부분의 시간을 &quot;결과가 도착하기를 기다리는&quot; 데 씁니다. CPU 가 연산하는 시간이 거의 없습니다. 이런 호출은 라이브러리 쪽에서 이미 비동기 버전을 제공합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ Before &amp;mdash; 동기 호출로 스레드를 통째로 점유
public string Fetch(string url)
{
    var client = new HttpClient();
    string html = client.GetStringAsync(url).Result; // 응답 올 때까지 스레드 블로킹
    return html.Trim();
}

// ✅ After &amp;mdash; await 로 외부에 위임, 스레드 자유
public async Task&amp;lt;string&amp;gt; FetchAsync(string url)
{
    var client = new HttpClient();
    string html = await client.GetStringAsync(url); // 응답 동안 스레드 점유 없음
    return html.Trim();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;After 의 IL 은 앞서 본 것처럼 상태 머신과 콜백 등록 코드로 변환됩니다. Before 의 IL 은 어떤지 보겠습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// Before: GetSync 의 핵심 IL &amp;mdash; 동기 .Result 호출
.method public hidebysig instance Task`1&amp;lt;string&amp;gt; GetSync(string url) cil managed
{
    IL_0000: ldsfld HttpClient Sample2::client
    IL_0005: ldarg.1
    IL_0006: callvirt Task`1&amp;lt;string&amp;gt; HttpClient::GetStringAsync(string)
    IL_000b: callvirt !0 Task`1&amp;lt;string&amp;gt;::get_Result() // &amp;larr; 여기서 스레드 블로킹
    IL_0010: callvirt string String::Trim()
    IL_0015: call Task`1&amp;lt;!!0&amp;gt; Task::FromResult&amp;lt;string&amp;gt;(!!0)
    IL_001a: ret
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;After 와 Before 의 IL 차이가 결정적입니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;After&lt;/b&gt; &amp;mdash; 컴파일러가 상태 머신 구조체와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;MoveNext&lt;/code&gt; 를 만들고, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCompleted&lt;/code&gt; 가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;false&lt;/code&gt; 면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AwaitUnsafeOnCompleted&lt;/code&gt; 로 콜백을 등록한 뒤 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;leave&lt;/code&gt; 로 메서드를 빠져나갑니다. 응답이 오면 시스템이 다시 진입해 다음 줄을 실행합니다. &lt;b&gt;스레드는 그 사이에 풀려 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Before&lt;/b&gt; &amp;mdash; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;string&amp;gt;::get_Result()&lt;/code&gt; 한 방으로 끝납니다. 짧고 단순하지만, 이 IL 한 줄은 &quot;응답이 올 때까지 이 스레드를 통째로 멈춘다&quot;는 뜻입니다. UI 스레드에서 호출하면 화면이 얼어붙고, ASP.NET 스레드 풀에서 호출하면 스레드 풀이 고갈됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;같은 비동기 API 를 부르는데 IL 한 줄을 잘못 쓰면 동작 모델 자체가 뒤집힙니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4-2. CPU 바운드: 내가 직접 무거운 계산을 한다면 Task.Run&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;I/O 와 정반대로 CPU 자원을 길게 점유하는 작업(이미지 디코딩, 복잡한 게임 로직 시뮬레이션, 대량 데이터 변환)은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 를 붙일 비동기 API 가 따로 없습니다. 그냥 메서드를 호출하면 그 스레드에서 CPU 가 돕니다.&lt;/p&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;메인 스레드(UI &amp;middot; Unity 메인 스레드)에서 무거운 계산을 직접 하면 화면이 얼어붙기 때문에&lt;/b&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt; 으로 스레드 풀에 일을 넘긴 뒤 그 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 를 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 합니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class HeavyCompute
{
    // ❌ Before &amp;mdash; 메인 스레드에서 직접 계산. UI 가 멈춘다.
    public int RunSync()
    {
        return Compute(); // CPU 100% 사용, 그동안 화면&amp;middot;이벤트 처리 정지
    }

    // ✅ After &amp;mdash; Task.Run 으로 스레드 풀에 위임 후 await
    public async Task&amp;lt;int&amp;gt; RunAsync()
    {
        int result = await Task.Run(() =&amp;gt; Compute());
        return result;
    }

    private int Compute()
    {
        int sum = 0;
        for (int i = 0; i &amp;lt; 100; i++) sum += i;
        return sum;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #2d2d2d; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #e5c07b; color: #282c34; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;IL&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 0 0 8px 8px; font-size: 13px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// After: RunAsync 의 핵심 IL &amp;mdash; Task.Run 도 결국 awaitable Task 를 반환
IL_0011: ldloc.1
IL_0012: ldftn instance int32 Sample3::'&amp;lt;RunAsync&amp;gt;b__0_0'() // 람다 본문
IL_0018: newobj Func`1&amp;lt;int32&amp;gt;::.ctor(...)
IL_001d: call Task`1&amp;lt;!!0&amp;gt; Task::Run&amp;lt;int32&amp;gt;(class Func`1&amp;lt;!!0&amp;gt;) // 스레드 풀에 작업 던지기
IL_0022: callvirt TaskAwaiter`1&amp;lt;int32&amp;gt; Task`1&amp;lt;int32&amp;gt;::GetAwaiter()
IL_002a: call TaskAwaiter`1&amp;lt;int32&amp;gt;::get_IsCompleted()
IL_002f: brtrue.s IL_006d
// 완료 안 됐으면 콜백 등록 후 메서드 종료 (메인 스레드는 자유)
IL_004a: call AsyncTaskMethodBuilder`1&amp;lt;int32&amp;gt;::AwaitUnsafeOnCompleted&amp;lt;...&amp;gt;(...)
IL_006f: call TaskAwaiter`1&amp;lt;int32&amp;gt;::GetResult()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.Run(...)&lt;/code&gt; 도 IL 레벨에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 의 표준 패턴을 그대로 따른다&lt;/b&gt;는 점입니다. 즉, &quot;다른 스레드에 일을 던지고 &amp;rarr; 호출자에게 즉시 제어권 반환 &amp;rarr; 끝나면 다음 줄 이어서 실행&quot; 입니다. I/O 와 차이는 단 하나, &lt;b&gt;누가 그 작업을 처리하는가&lt;/b&gt;입니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;패턴&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;작업을 처리하는 주체&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;호출자 스레드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;I/O 바운드 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await client.GetStringAsync(...)&lt;/code&gt;)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;OS / 네트워크 카드 / 디스크 컨트롤러&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;자유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;CPU 바운드 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.Run(() =&amp;gt; Compute())&lt;/code&gt;)&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;스레드 풀의 다른 스레드&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;자유&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote style=&quot;background: #fff5f5; border-left: 4px solid #fa5252; border-radius: 0 8px 8px 0; padding: 14px 18px; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Unity&lt;/code&gt; 메인 스레드 주의&lt;/b&gt;: Unity 의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Transform&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GameObject&lt;/code&gt;, 대부분의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UnityEngine.*&lt;/code&gt; API 는 메인 스레드에서만 호출해야 합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt; 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;transform.position = ...&lt;/code&gt; 을 호출하면 예외가 터집니다. 따라서 Unity 에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt; 으로 보낼 수 있는 일은 순수 C# 계산(파싱, 압축, 게임 로직 시뮬레이션)에 한정됩니다. 코루틴이나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UniTask.SwitchToMainThread()&lt;/code&gt; 로 메인 스레드 복귀 패턴을 함께 익히는 게 좋습니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;4-3. 라이브러리 API 의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;DoAsync&lt;/code&gt; 규약 &amp;mdash; 보이면 무조건 그쪽&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;.NET BCL 과 잘 만든 외부 라이브러리는 동일 작업에 대해 &lt;b&gt;동기 버전과 비동기 버전을 함께 제공&lt;/b&gt;합니다. 그리고 &lt;b&gt;비동기 버전에는 반드시 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Async&lt;/code&gt; 접미사가 붙어 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;1c&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// File.ReadAllText vs File.ReadAllTextAsync
// HttpClient.Send vs HttpClient.SendAsync
// SqlCommand.ExecuteReader vs SqlCommand.ExecuteReaderAsync
// Stream.Read vs Stream.ReadAsync&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;규칙은 단순합니다.&lt;/p&gt;
&lt;blockquote style=&quot;background: #f0f4ff; border-left: 4px solid #4263eb; border-radius: 0 8px 8px 0; padding: 14px 18px; color: #364fc7; font-size: 14px; line-height: 1.8; margin: 16px 0;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드 안에서 같은 작업의 동기 버전과 비동기 버전이 모두 보이면, 무조건 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Async&lt;/code&gt; 가 붙은 쪽을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 한다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이유는 IL 비교에서 본 그대로입니다. 비동기 버전을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 로 호출하면 호출 스레드가 풀려나가지만, 동기 버전을 그대로 호출하면 그 스레드는 작업이 끝날 때까지 통째로 점유됩니다. ASP.NET 같은 서버 환경에서는 처리량이 직접 떨어지고, Unity 메인 스레드에서는 화면이 얼어붙습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ async 메서드 안에서 동기 API 호출 &amp;mdash; async 의 의미가 사라짐
public async Task&amp;lt;string&amp;gt; LoadConfigBadAsync()
{
    string text = File.ReadAllText(&quot;config.json&quot;); // 스레드 블로킹
    return text;
}

// ✅ 같은 메서드의 비동기 버전을 await
public async Task&amp;lt;string&amp;gt; LoadConfigGoodAsync()
{
    string text = await File.ReadAllTextAsync(&quot;config.json&quot;);
    return text;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;라이브러리에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Async&lt;/code&gt; 버전이 없으면 어쩔 수 없이 동기 버전을 써야 하지만, &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Async&lt;/code&gt; 가 있는데 동기 버전을 쓰는 것은 명백한 실수&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 5 ===================== --&gt;
&lt;h2 id=&quot;section-5&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;5. [함정과 주의사항] &amp;mdash; .Result/.Wait, fire-and-forget, async void 남용&lt;/h2&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5-1. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Wait()&lt;/code&gt; 로 동기 흐름에 끼워 넣기 &amp;mdash; Deadlock 위험&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 메서드를 호출하는 코드를 만들었는데, 호출하는 쪽이 동기 메서드라서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 를 못 붙이는 상황이 생깁니다. 가장 흔한 잘못된 해결책이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt; 나 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Wait()&lt;/code&gt; 입니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ async 코드와 동기 코드를 .Result 로 억지로 붙이는 패턴
public string LoadSync()
{
    return LoadAsync().Result; // UI 스레드/ASP.NET 컨텍스트에서 deadlock 가능
}

private async Task&amp;lt;string&amp;gt; LoadAsync()
{
    var html = await client.GetStringAsync(&quot;https://...&quot;);
    return html.Trim(); // &amp;larr; 이 줄은 원래 호출 스레드(UI)로 돌아가고 싶어함
}

// ✅ async 를 끝까지 전파(async all the way)
public async Task&amp;lt;string&amp;gt; LoadAsync2()
{
    return await LoadAsync();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Deadlock 의 메커니즘은 거칠게 말해 이렇습니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;UI 스레드가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;LoadAsync().Result&lt;/code&gt; 로 자신을 블로킹.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;LoadAsync&lt;/code&gt; 내부의 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 가 끝나면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;html.Trim()&lt;/code&gt; 줄을 &lt;b&gt;원래 호출 스레드(UI 스레드)&lt;/b&gt; 에서 실행하고 싶어 합니다.&lt;/li&gt;
&lt;li&gt;그런데 UI 스레드는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt; 때문에 막혀 있습니다.&lt;/li&gt;
&lt;li&gt;서로 무한 대기.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait(false)&lt;/code&gt; 같은 우회 수단도 있지만, 본질적인 해결은 &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 를 끝까지 일관되게 전파하는 것&lt;/b&gt;(async all the way)입니다. 어디선가 동기로 끊는 순간 비동기의 이점이 사라지고 deadlock 위험이 생깁니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5-2. 반환된 Task 를 무시 &amp;mdash; Fire-and-forget 의 함정&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 를 받아 놓고 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 도 안 하고 변수에 담지도 않으면, 그 작업은 백그라운드에서 흘러갑니다. 예외가 터져도 알 길이 없습니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// ❌ Task 를 그냥 버린다 &amp;mdash; 컴파일러 경고 CS4014 가 뜨지만 무시되곤 함
public async Task SaveUserAsync(User user)
{
    db.WriteAsync(user); // await 누락
    log.WriteAsync($&quot;Saved {user.Id}&quot;); // await 누락
    // 메서드는 두 작업이 끝나기도 전에 반환됨
}

// ✅ 반드시 await
public async Task SaveUserAsync2(User user)
{
    await db.WriteAsync(user);
    await log.WriteAsync($&quot;Saved {user.Id}&quot;);
}

// ✅ 정말 fire-and-forget 이 의도라면 명시적으로 _ 에 대입하고 예외 처리 책임을 진다
public void FireAndForget(User user)
{
    _ = Task.Run(async () =&amp;gt;
    {
        try { await audit.WriteAsync(user); }
        catch (Exception ex) { logger.LogError(ex, &quot;audit 실패&quot;); }
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;대부분의 경우 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 를 빼먹은 것은 버그입니다. 정말 의도한 fire-and-forget 이라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_ =&lt;/code&gt; 로 명시하고 내부에서 예외를 잡아야 합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5-3. async void 남용 &amp;mdash; 버튼 클릭 핸들러를 일반 헬퍼 메서드로 재사용&lt;/h3&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;이벤트 핸들러는 어쩔 수 없이 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 지만, 그 메서드를 일반 메서드처럼 다른 곳에서 호출하면 안 됩니다.&lt;/p&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class BadHandler
{
    // 이벤트 시그니처상 async void &amp;mdash; 여기까지는 OK
    private async void OnButtonClick(object sender, EventArgs e)
    {
        await DoSomethingAsync();
    }

    // ❌ 이 메서드를 다른 곳에서 호출 &amp;mdash; 추적 불가, 예외 손실
    public void RetryAll()
    {
        OnButtonClick(this, EventArgs.Empty);
        OnButtonClick(this, EventArgs.Empty);
        // 두 번 모두 끝났는지 알 수 없고, 실패해도 못 잡음
    }
}

public class GoodHandler
{
    // 실제 로직은 Task 반환 메서드로 분리
    public async Task DoSomethingAsync() { /* ... */ }

    // 이벤트 핸들러는 진짜 한 줄짜리 어댑터
    private async void OnButtonClick(object sender, EventArgs e)
    {
        try { await DoSomethingAsync(); }
        catch (Exception ex) { ShowError(ex); }
    }

    public async Task RetryAllAsync()
    {
        await DoSomethingAsync();
        await DoSomethingAsync();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;규칙은 단순합니다. &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 는 이벤트 핸들러의 어댑터 한 줄짜리에서만&lt;/b&gt;, 실제 로직은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 반환 메서드에서 작성합니다.&lt;/p&gt;
&lt;h3 style=&quot;font-size: 17px; font-weight: bold; color: #364fc7; margin: 32px 0 12px; padding-bottom: 6px; border-bottom: 2px solid #e7f5ff;&quot; data-ke-size=&quot;size23&quot;&gt;5-4. Unity 모바일에서의 추가 함정 &amp;mdash; UnityEngine API 와 GC&lt;/h3&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class BadCoroutineLikeAsync : MonoBehaviour
{
    // ❌ Task.Run 안에서 UnityEngine API 호출
    public async Task LoadAsync()
    {
        await Task.Run(() =&amp;gt;
        {
            transform.position = Vector3.zero; // 메인 스레드 외 호출 &amp;rarr; 예외
        });
    }

    // ❌ Update 마다 async 메서드를 호출하며 await 누락 &amp;mdash; 매 프레임 fire-and-forget
    private void Update()
    {
        FetchOptionalAsync(); // Task 누수 + 예외 손실
    }
    private async Task FetchOptionalAsync() { await Task.Yield(); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;public class GoodAsyncInUnity : MonoBehaviour
{
    // ✅ Task.Run 에는 순수 C# 계산만, UnityEngine API 는 메인 스레드 복귀 후
    public async Task LoadAsync()
    {
        byte[] data = await DownloadAsync();
        var parsed = await Task.Run(() =&amp;gt; HeavyParse(data)); // 순수 C# 계산
        // 여기는 다시 메인 스레드 (Task.Run 이 완료된 뒤 await 가 복귀시킴)
        transform.position = parsed.Spawn;
    }

    private Task&amp;lt;byte[]&amp;gt; DownloadAsync() =&amp;gt; Task.FromResult(new byte[] { });
    private SpawnInfo HeavyParse(byte[] _) =&amp;gt; new() { Spawn = Vector3.zero };
    private struct SpawnInfo { public Vector3 Spawn; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;Unity 모바일에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 한 번 생성에도 힙 할당과 GC 부담이 따릅니다. 매 프레임 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 호출이 발생하는 핫패스에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 대신 &lt;b&gt;UniTask&lt;/b&gt;(Cysharp 의 GC-zero 비동기 라이브러리)를 도입하는 것을 강력히 추천합니다. 코루틴과 비교해도 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;try/catch&lt;/code&gt;, 반환값, 다중 await 가 자연스럽다는 장점이 있습니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 6 ===================== --&gt;
&lt;h2 id=&quot;section-6&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;6. [C# 버전별 변화] &amp;mdash; 시작은 5.0, 점차 확장&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 16px 0;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;버전&lt;/th&gt;
&lt;th style=&quot;background: #f1f3f5; color: #1a1a1a; font-weight: 600; border-bottom: 2px solid #364fc7; padding: 12px 16px; text-align: left;&quot;&gt;변화&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;C# 5.0 (.NET 4.5)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 키워드 도입. 반환형은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;C# 7.0 (.NET 4.7 / Core 2.0)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; 도입. 동기 완료가 잦은 비동기 메서드의 힙 할당 회피.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;C# 7.1&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Main&lt;/code&gt; &amp;mdash; 콘솔 앱 진입점에 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 허용.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #f8f9fa;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;C# 8.0&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;비동기 스트림 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await foreach&lt;/code&gt;) &amp;mdash; 결과를 여러 번 산출하는 비동기 시퀀스 표현.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background: #fff;&quot;&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;&lt;b&gt;C# 9.0+&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 11px 16px; border-bottom: 1px solid #e9ecef;&quot;&gt;모듈 초기화&amp;middot;기타 컴파일러 최적화. 사용자 코드 시그니처 변화는 거의 없음.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;margin: 16px 0;&quot;&gt;
&lt;div style=&quot;background: #21252b; border-radius: 8px 8px 0 0; padding: 8px 16px; display: flex; align-items: center; gap: 8px;&quot;&gt;&lt;span style=&quot;background: #61afef; color: #fff; font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; font-family: 'JetBrains Mono',monospace;&quot;&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0 0 8px 8px; font-size: 14px; line-height: 1.7; font-family: 'JetBrains Mono',monospace; overflow-x: auto; margin: 0;&quot;&gt;&lt;code&gt;// C# 5.0 (.NET 4.5) &amp;mdash; 첫 등장 시 가능한 모양
public async Task&amp;lt;int&amp;gt; CountAsync_v5(string path)
{
    using var reader = new StreamReader(path);
    int count = 0;
    while (await reader.ReadLineAsync() != null) count++;
    return count;
}

// C# 7.0 + : ValueTask&amp;lt;T&amp;gt; 로 동기 완료 경로 최적화
public ValueTask&amp;lt;int&amp;gt; CountCachedAsync(string key)
{
    if (cache.TryGetValue(key, out int v))
        return new ValueTask&amp;lt;int&amp;gt;(v); // 힙 할당 0
    return new ValueTask&amp;lt;int&amp;gt;(LoadAndCacheAsync(key));
}

// C# 7.1 + : async Main
public static async Task&amp;lt;int&amp;gt; Main(string[] args)
{
    await SomeStartupAsync();
    return 0;
}

// C# 8.0 + : 비동기 스트림
public async IAsyncEnumerable&amp;lt;string&amp;gt; StreamLinesAsync(string path)
{
    using var reader = new StreamReader(path);
    string? line;
    while ((line = await reader.ReadLineAsync()) is not null)
        yield return line;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;margin: 0 0 16px; font-size: 16px; line-height: 1.9;&quot; data-ke-size=&quot;size16&quot;&gt;신입 개발자 단계에서는 &lt;b&gt;C# 5.0 의 기본 모양&lt;/b&gt;(&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async Task&amp;lt;T&amp;gt;&lt;/code&gt; + &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;) 만 손에 익혀도 충분합니다. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt; 와 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IAsyncEnumerable&lt;/code&gt; 은 측정&amp;middot;필요성 판단 후 도입하는 도구입니다.&lt;/p&gt;
&lt;hr style=&quot;border: none; border-top: 1px solid #e9ecef; margin: 40px 0;&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;!-- ===================== SECTION 7 ===================== --&gt;
&lt;h2 id=&quot;section-7&quot; style=&quot;font-size: 22px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #4263eb; padding-left: 14px; margin: 48px 0 20px;&quot; data-ke-size=&quot;size26&quot;&gt;7. [정리] &amp;mdash; 핵심 체크리스트&lt;/h2&gt;
&lt;ul style=&quot;margin: 12px 0 16px; padding-left: 24px; font-size: 15px; line-height: 2;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 한정자는 &quot;이 메서드 안에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 를 쓰겠다&quot;는 컴파일러 신호. 그 자체로 백그라운드 실행은 아니다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt; 의 의미는 &quot;기다림&quot;이 아니라 &lt;b&gt;&quot;제어권 양보 + 콜백 등록&quot;&lt;/b&gt;. IL 레벨에서 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetAwaiter()&lt;/code&gt; &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;IsCompleted&lt;/code&gt; 검사 &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;AwaitUnsafeOnCompleted&lt;/code&gt; 등록 &amp;rarr; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetResult()&lt;/code&gt; 패턴으로 펼쳐진다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드의 반환형은 5종. &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;void&lt;/code&gt;(이벤트 핸들러 한정)&lt;/b&gt;. 기본은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async void&lt;/code&gt; 는 이벤트 핸들러 외에서는 절대 금지&lt;/b&gt; &amp;mdash; 완료 추적 불가, 예외 처리 불가, 프로세스 강제 종료 위험.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;I/O 바운드는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await client.SomethingAsync(...)&lt;/code&gt;&lt;/b&gt;, &lt;b&gt;CPU 바운드는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await Task.Run(() =&amp;gt; Compute())&lt;/code&gt;&lt;/b&gt;. 둘 다 호출 스레드를 풀어준다는 점에서 동일하지만, 작업을 처리하는 주체가 다르다.&lt;/li&gt;
&lt;li&gt;[ ] 라이브러리 API 가 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;DoAsync&lt;/code&gt; 형태를 제공하면 &lt;b&gt;무조건 비동기 버전을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;&lt;/b&gt;. 동기 버전을 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 메서드 안에서 부르면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 의미가 사라진다.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b&gt;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Result&lt;/code&gt; / &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;.Wait()&lt;/code&gt; 는 deadlock 의 지름길.&lt;/b&gt; &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;async&lt;/code&gt; 를 끝까지 전파(async all the way)한다.&lt;/li&gt;
&lt;li&gt;[ ] 반환된 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 는 반드시 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;await&lt;/code&gt;. fire-and-forget 이 의도라면 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;_ =&lt;/code&gt; 로 명시하고 내부에서 예외를 잡는다.&lt;/li&gt;
&lt;li&gt;[ ] 메서드 이름은 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;PascalCaseAsync&lt;/code&gt; 규약 (&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;GetUserAsync&lt;/code&gt;, &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;LoadConfigAsync&lt;/code&gt;). 이벤트 핸들러는 예외.&lt;/li&gt;
&lt;li&gt;[ ] Unity 모바일 핫패스에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task&lt;/code&gt; 의 GC 부담을 의식하고, 필요시 &lt;b&gt;UniTask&lt;/b&gt; 도입. &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;Task.Run&lt;/code&gt; 안에서는 &lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;UnityEngine.*&lt;/code&gt; API 호출 금지.&lt;/li&gt;
&lt;li&gt;[ ] 상태 머신&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;ConfigureAwait&lt;/code&gt;&amp;middot;&lt;code style=&quot;background: #f1f3f5; color: #e03131; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; font-family: 'JetBrains Mono',monospace;&quot;&gt;SynchronizationContext&lt;/code&gt; 의 깊은 동작은 별도 심화 주제로 분리. 본 글의 사용 패턴만 익히면 99% 실수는 피할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>C# 기초</category>
      <category>C#</category>
      <category>기초</category>
      <category>입문</category>
      <author>EveryDay.DevUp</author>
      <guid isPermaLink="true">https://everyday-devup.tistory.com/576</guid>
      <comments>https://everyday-devup.tistory.com/576#entry576comment</comments>
      <pubDate>Fri, 8 May 2026 23:38:21 +0900</pubDate>
    </item>
  </channel>
</rss>