목차
코루틴 예외 처리와 자원 정리
코루틴 예외 처리와 자원 정리
코루틴을 사용하다보면 StopCoroutine을 사용해야할 때가 있다.
이럴 경우, 언로드하거나, 초기화하거나, 아무튼 진행을 완료하고 종료를 해줘야하는 상황이 생긴다.
이번에는 오브젝트 풀에서 자원을 꺼내서 쓰고 다시 돌려놔야하는데 코루틴이 종료되면 릴리즈가 되지 않아 다음번에 오브젝트를 새로 생성해야하는 리소스 낭비가 생기게 된다.
시도: try { } finally { }
public override IEnumerator EffectCoroutine(EffectOrder order, GameObject target = null)
{
if (target == null)
target = EffectManager.Instance.gameObject;
var op = ObjectPoolManager.Instance.GetObjectAsync(path, target.transform);
try
{
while (!op.IsCompleted)
yield return null;
yield return new WaitForSeconds(Duration);
}
finally
{
ObjectPoolManager.Instance.ReleaseObject(path, op.Result);
}
}
코루틴 자원정리를 검색해봤을때 try { } 로 감싸고 finally { }로 종료 시 실행을 보장 받길 원하는 부분을 작성하면 된다고 봤다.
근데!!!
적용이 되지 않는 것 같았다.
TIL에 쓰기 위해서 찾아보는 와중에 내가 찾아봤던 건 코틀린의 코루틴이었다는 것을 알게 되었다.
고민: StopCoroutine을 사용해서 강제 종료 대신 간접적으로 종료하게 만들자
오늘은 시간이 늦어서 적용을 못하고 퇴실하겠지만!!!
일단 아이디어는 찾아왔다.
StopCoroutine으로 코루틴을 외부에서 강제로 죽이지 않고,
코루틴 내부의 루프가 특정 조건으로 인해 자연스럽게 종료되도록 설계하면 된다!
private bool isActive = true;
IEnumerator MyCoroutien()
{
//task
while(isActive)
{
//작업
}
//리소스 해제 등 코루틴 종료 시 진행되어야하는 작업들
}
public void EndTask()
{
isActive = false;
}
위와 같이 구현하면 될 것 같다.
마침, 각 EffectDataSO는 각각 하나의 코루틴만을 갖고 있고, 하나의 BaseEffectData를 상속 받고 있으니 이 곳에 abstract EndTask를 선언해주면 되기도 하다.
하지만 이는 모든 코루틴에 적용하기에는 매번 isActive와 같은 플래그를 생성해서 관리하기에는 코드 가독성이나 관리가 불편해질 것 같다.
아이디어: 강제종료를 대비하기 위해서는 코루틴보다는 async/await을 이용하자?
라고 Gemini한테 고민을 털어놨을 때 대답했다.
근데 아무리 생각해도 비동기로 진행하는 건 프레임 단위로 대기하고 종료해야하는 방법에는 맞지 않을 것 같았다.
이러한 이유로 코루틴을 사용하는 것을 고집하고 싶었다.
결론: 일단은 코루틴을 간접종료하는 방식을 도입하자!!
검색을 계속 하다보니 UniTask에 대한 힌트를 얻을 수 있었지만,
예시를 확인해봐도 당장 공부해서 도입하기에는 부담스럽다는 생각이 들었다.
일단 팀원들이 이해하기 쉬운 코드를 유지하는 것도 중요하다고 생각하기도 하고,당장 시간이 여유로운 편은 아니라서 당장의 유지보수를 위해 간접 종료하는 방식으로 빠르게 구현해두고 추후 리팩토링을 주말이나 추석과 같이, 팀 일정과 별개로 시간을 내서 진행해보려고 한다.
UniTask: 추후 같은 문제를 발견했을 때를 대비해서 UniTask를 공부해두자!
UniTask는 유니티 환경에서 IEnumerator 코루틴을 대체하여 C#의 async/await 패턴을 고성능으로 사용할 수 있게 해주는 서드파티 라이브러리다.
코루틴처럼 유니티의 메인 스레드에서 작동하면서도, C#의 비동기 기능을 활용하여 코루틴의 근본적인 문제점을 해결할 수 있다.
[CreateAssetMenu(fileName = "SpriteEffectData", menuName = "ScriptableObjects/Effects/SpriteEffectData", order = 6)]
public class SpriteEffectData : BaseEffectData
{
[field: SerializeField] public float Duration { get; private set; }
[field: SerializeField] public string path { get; private set; }
// 비동기 로직과 자원 정리를 처리
public override async UniTask EffectAsync(EffectOrder order, GameObject target, CancellationToken token)
{
if (target == null)
target = EffectManager.Instance.gameObject;
GameObject effectInstance = null; // finally에서 참조할 변수
var op = ObjectPoolManager.Instance.GetObjectAsync(path, target.transform);
try
{
// 2. 획득 대기 (GetObjectAsync가 완료될 때까지 기다림)
await UniTask.WaitUntil(() => op.IsCompleted, PlayerLoopTiming.Update, token);
// 3. 자원 획득 완료 (위 WaitUntil이 완료되면 op.Result에 접근 가능)
effectInstance = op.Result;
// 4. 작업 대기 (Duration)
// 취소 토큰을 전달하여, 대기 중 취소되면 여기서 예외 발생
await UniTask.Delay((int)(Duration * 1000), cancellationToken: token);
}
catch (OperationCanceledException)
{
// 5. 취소 요청을 받으면 이 블록이 실행됨
Debug.LogWarning($"[{path}] 이펙트가 취소되었습니다. finally에서 정리됩니다.");
}
catch (System.Exception ex)
{
// 6. 기타 예외 발생 시
Debug.LogError($"[{path}] 이펙트 실행 중 오류: {ex.Message}");
}
finally
{
// 7. 가장 중요: 자원 릴리즈를 무조건 보장함.
if (effectInstance != null)
{
ObjectPoolManager.Instance.ReleaseObject(path, effectInstance);
Debug.Log($"[{path}] finally에서 안전하게 풀에 반환 완료.");
}
}
}
}
아래와 같이 장점과 단점을 비교해볼 수 있다.
자원누수 문제에서
유니티 코루틴은 StopCoroutine시 finally 실행이 보장되지 않아 자원 누수 문제가 발생하는데 반해,
UniTask는 CancellatiionToken 취소 시 finally를 실행 보장할 수 있다.
성능(GC Alloc)문제에서
유니티 코루틴은 yield return마다 메모리를 할당하여 GC의 부담이 큰 반면에,
UniTask는 메모리 할당(Alloc)이 거의 없어 성능이 뛰어나다.
비동기 제어 면에서
유니티 코루틴은 복잡하고 결과 반환이 어렵지만
UniTask는 일단 C#처럼 try-catch-finally로 명확하게 제어할 수 있다.
즉,
취소 시 자원 정리를 보장할 수 있고,
메모리 할당을 최소화하여 성능이 우수하고,
코루틴과 비슷하게 프레임 단위의 세밀한 제어(Yield, WaitForFixedUpdate)도 가능하다.
서드파티 라이브러리라 처음 들어봐서 어색한 점은 빼면 단점이 거의 보이지 않는 것 같다.
꼭 한 번은 써보면 좋겠다는 생각이 들었다. (이렇게 정리해보니 익숙해진 것 같은 느낌에.. 어쩌면 내일 당장 도입할지도 모르겠다.)
느낀점
팀 내에서 집집시라고 방해금지 시간대를 정했smsep 하필이면 그 시간에 집중력이 떨어지는 바람에... ㅜㅜ
계획한 만큼 많은 작업을 하지 못했다.
그래도 오늘은 끝낸 줄 알았던 EffectManager...
TIL을 쓰면서 코루틴 중단 시 ObjectPool에서 꺼내온 오브젝트의 릴리즈가 제대로 되고 있지 않음을 파악하고 공부하느라 고생을 좀 한 것 같다.
하지만 EffectManager의 치명적인 리소스 누수 버그(StopCoroutine과 finally 미실행 문제) 를 발견하고 UniTask라는 근본적인 해결책까지 학습한 것은, 단순히 기능을 구현하는 것보다 훨씬 높은 가치와 실무 역량을 얻은 하루라고 생각하기로 했다!
내일 학습 할 것은 무엇인지
무튼 그렇게 되서!!
내일은 오늘 배운 UniTask를 적용해볼 것 같다.
장점만 가득한 방법이라고 생각하는데, 단점은 아무래도 직접 사용해보면서 경험을 해봐야할 것 같아서!
이런거 경험해보라고 최종프로젝트를 해보는 거 아닐까 싶어서!!
내일 오전에 후딱 진행해버리려고 한다.
'부트캠프 > 본캠프' 카테고리의 다른 글
| [내일배움캠프_2025SEP27] 보스 몬스터 특수 스킬 구현 (0) | 2025.09.27 |
|---|---|
| [내일배움캠프_2025SEP26] UniTask 도입 (0) | 2025.09.26 |
| [내일배움캠프_2025SEP24] 모의면접 2차, EffectData SO 관리, 오브젝트 풀 매니저 (0) | 2025.09.24 |
| [내일배움캠프_2025SEP23] 모의면접 2차 스터디, EffectManager 시작 (0) | 2025.09.23 |
| [내일배움캠프_2025SEP22] 이펙트(카메라), 애니메이션 (0) | 2025.09.22 |