본문 바로가기
UNITY/네트워크

[네트워크] 비동기 통신

by 램플릿 2025. 6. 11.

 

비동기 통신

 

  Asynchronous (비동기) 통신   

Asynchronous(비동기) 통신은 프로그램이 특정 작업(예: 네트워크 요청)을 시작한 후, 그 작업의 완료를 기다리지 않고 바로 다음 코드를 실행하는 통신 방식입니다. 작업의 결과는 나중에 콜백(callback) 함수, 이벤트, future/promise 객체 등을 통해 확인하거나 처리합니다.

 

 

  비동기 TCP/IP 통신의 장단점   

장점
UI 멈춤 방지:네트워크 작업 중에도  프로그램 실행이 중단(block)되지 않아, UI가 멈추는 현상(Freezing)을 방지할 수 있습니다.
높은 응답성:사용자의 입력에 즉각적으로 반응할 수 있어 애플리케이션의 응답성이 유지됩니다.
효율적인 자원 사용:대기 시간 동안 다른 작업을 병렬 처리할 수 있으므로, CPU 자원을 효율적으로 사용할 수 있습니다.
확장성:다수의 클라이언트를 동시에 처리하기에 유리합니다.


단점
구현 복잡도 증가:콜백 함수, 이벤트, async/await 등 비동기 처리를 위한 추가적인 메커니즘이 필요하므로, 동기 방식보다 코드가 복잡해질 수 있습니다.
디버깅 어려움:코드의 실행 흐름이 여러 콜백 함수나 비동기 메서드에 분산되므로, 디버깅이 어려울 수 있습니다.
오류 처리 복잡성:예외 처리가 여러 곳에 분산될 수 있어, 오류 처리가 더 복잡해질 수 있습니다. (try-catch블록 사용 시 주의 필요)

 

 

  비동기 방식 사용 적합성   

비동기(Asynchronous) 방식이 효율성을 높여주는 것은 맞지만, 모든 프로그램을 비동기로 작성하는 것이 항상 좋은 선택은 아닙니다.


✅ 비동기의 장점

  1. I/O 작업 효율성: 파일 읽기/쓰기, 네트워크 요청, 데이터베이스 쿼리처럼 대기 시간이 긴 작업에서 CPU를 놀리지 않고 다른 작업을 수행할 수 있어 효율적입니다.
  2. 높은 동시성: 많은 요청을 빠르게 처리해야 하는 웹 서버나 클라우드 서비스에 적합합니다.
  3. 자원 절약: 스레드를 많이 생성하지 않아도 되므로 메모리와 CPU 자원을 절약할 수 있습니다.

❌ 하지만, 비동기를 무조건 쓰면 안 되는 이유

  1. 복잡성 증가: 비동기 코드(async/await, 콜백 등)는 흐름을 따라가기 어렵고, 디버깅이 어려워질 수 있어요.
  2. CPU 바운드 작업에는 효과 없음: 계산이 많이 필요한 작업(예: 이미지 처리, 수학 계산 등)은 동기 방식 또는 멀티스레딩/멀티프로세싱이 더 효과적입니다.
  3. 오버엔지니어링의 위험: 단순한 스크립트나 사용자가 거의 없는 앱에 비동기를 적용하면 코드는 복잡해지고, 얻는 이득은 거의 없음.
  4. 호환성 문제: 비동기 코드는 일부 라이브러리나 프레임워크와 충돌하거나 특정 환경에서 동작이 어려울 수 있음.

🔍 비동기 vs 동기, 언제 쓸까?

웹 서버에서 수천 명의 동시 접속 처리 비동기 (async)
파일 업로드나 다운로드 처리 비동기
수학 계산, 머신러닝 모델 훈련 동기 + 멀티프로세싱
간단한 스크립트나 CLI 툴 동기
복잡하지 않은 데스크탑 앱 동기 또는 혼합

📌 결론

비동기는 강력한 도구이지만, 그에 따르는 복잡성도 크기 때문에 상황에 맞춰 사용하는 것이 중요합니다.
"효율성"을 얻기 위해 "유지보수성"과 "안정성"을 희생하지 않도록 균형 있게 사용하는 것이 가장 좋은 접근입니다.

 


 

Unity에서의 비동기 통신

  Unity에서의 async/await와 Coroutine   

C#과 Unity에서는 async/await가 매우 유용하게 쓰일 수 있지만, 주의하지 않으면 문제가 발생할 수 있는 경우도 있습니다. 특히 Unity는 일반적인 C# .NET 환경과 다른 특성이 있기 때문에 몇 가지 중요한 주의점이 있어요.


✅ C#에서 async/await의 일반적인 장점

  • 비동기 작업의 가독성 향상: 콜백 지옥 없이 깔끔한 코드 작성 가능
  • I/O 바운드 작업에 적합: 네트워크 요청, 파일 읽기, DB 쿼리 등에서 효율적
  • 메인 스레드 차단 방지: UI 응답성을 유지할 수 있음 (특히 WPF, WinForms에서 중요)

❗ Unity에서 async/await 사용 시 주의할 점

1. Unity는 싱글 스레드 기반이다

  • Unity의 모든 GameObject 및 대부분의 Unity API는 메인 스레드에서만 안전하게 호출 가능해요.
  • await 이후의 코드가 다른 스레드에서 실행되면, Unity 객체에 접근하려다 크래시나 예외가 발생할 수 있어요.

해결 방법: await 이후에는 반드시 메인 스레드에서 실행되도록 해야 합니다.

await Task.Delay(1000); // 백그라운드에서 대기
// Unity API 사용 전, 메인 스레드로 돌아와야 안전
UnityMainThreadDispatcher.Instance().Enqueue(() => {
    someGameObject.SetActive(true);
});

2. Unity의 코루틴과 async/await의 충돌

  • Unity는 코루틴(Coroutine) 시스템이 이미 비동기적 작업에 특화되어 있음
  • async/await과 IEnumerator를 혼용하면 흐름 제어가 어려워지고 문제가 생길 수 있음

🟨 예시: 둘 다 쓰면 언제 어떤 작업이 먼저 끝날지 추적이 힘들 수 있음


3. Task 누수 (메모리 문제)

  • async 메서드에서 예외 처리를 안 하거나 await 결과를 무시하면 작업이 백그라운드에서 계속 살아있는 상태가 돼 메모리 누수가 생길 수 있어요.
// 잘못된 사용: 예외가 발생해도 감지 못함
SomeAsyncMethod(); // 경고: await 안 함

// 올바른 예: try-catch로 예외 처리
try {
    await SomeAsyncMethod();
} catch (Exception ex) {
    Debug.LogError(ex);
}

4. 플랫폼 호환성

  • 일부 Unity 타겟 플랫폼(특히 WebGL, 일부 모바일)은 async/await과 관련된 스레드 기능이 제한적입니다.
  • WebGL은 멀티스레드를 지원하지 않기 때문에 Task.Run 같은 기능은 작동하지 않아요.

🔍 Unity에서 async/await을 안전하게 쓰는 팁

  1. await 이후 Unity API를 사용할 때는 메인 스레드 보장 필요
  2. 코루틴 대신 async를 쓸지 명확히 구분
  3. Task.Run 같은 백그라운드 작업은 최소화, 그리고 필요하면 결과를 메인 스레드로 다시 전달
  4. async void 지양하고 가능하면 async Task로 작성
  5. 예외는 반드시 try-catch로 잡기

✅ 언제 Unity에서 async/await을 쓰면 좋을까?

작업 종류 권장 방식

웹 요청, 서버 통신 async/await (ex. HttpClient)
로컬 파일 읽기 async/await
유저 입력 대기, 타이밍 제어 Coroutine 사용 권장
Unity API 호출이 필요한 반복 작업 Coroutine 사용 권장

📌 결론

Unity에서 async/await을 사용할 수는 있지만, Unity의 메인 스레드 모델과 호환되지 않는 부분을 잘 이해하고 신중히 사용해야 합니다. 적절히 코루틴과 조합하거나, UnityMainThreadDispatcher 같은 유틸을 활용하면 더 안전하고 강력하게 사용할 수 있어요.