제목에서 알수 있듯이, 비동기 프로그래밍을 짤 때, async void는 지양하는 것이 좋다.
그이유로는 에러 처리에 있어서 예상치 못한 문제를 야기 할수가 있어서 그렇다. 그렇다면, 이것이 대체 어떻게 우리 프로그램에 영향을 끼치는 것일까.
결론부터 보자면, async void의 execution이 에러를 일으키면 application은 crash된다.
예를 들어보겟다.
BackGroundTask task = new();
Debug.Log("hello");
await task.RunTask();
Debug.Log("world");
public class BackGroundTask : Monobehaviour{
public async Task RunTask(){
await Task.Delay(1000);
Debug.Log("waiting...");
}
}
위의 코드에서 보면 hello를 출력하고 1초 기다린 이후 world를 출력한다. world를 출력하기 전에 BackGroundTask를 기다려준다.
하지만, 만약에 BackGroundTask를 기다리지 않고, hello world를 출력하고 BackGroundTask는 fire & forget해버리고 싶다면, 어떻게 해야겠는 가? 이론적으로 생각해보면 RunTask를 void 타입으로 치환 해주면 되지 않을까 싶다. 그리고 await를 사용하지않고 실행하면 될 것이다. 실제로 돌려보면 아무런 문제가 없다.
하지만, RunTask에서 문제가 생긴다면 어떻게 될까. RunTask에 error가 생기도록 코드를 바꿔보았다.
public class BackGroundTask : Monobehaviour{
public async Task RunTask1(){
await Task.Delay(1000);
Debug.Log("waiting1...");
await RunTask2();
}
public async Task RunTask2(){
await Task.Delay(1000);
Debug.Log("waiting2...");
try{
int[] numbers = {1,2};
Debug.Log(numbers[10]);
}catch (Exception e){
Debug.LogError(e.message)
}
}
}
위의 코드로 바꾼후 실행하면, 프로그램이 crash난다는 것을 알수가 있다.
그러면 task.RunTask();를 try/catch문으로 감싸주면 되지 않나? 이것으론 문제를 해결할수가 없다.
위의 그림처럼 메인 스레드에 알리지 않고, 서브 쓰레드에서 에러가 나버리면서, 프로그램을 닫아버리게 한다.
그리하여, 이를 해결하는 방법 중 하나는 Task.Run()으로 task실행 부분을 감싸 버리는 것이다.
이또한 이상적인 방법은 아니다.
오직, C#에서만 async/void를 지원한다고 한다. 그이유는, eventhandler용으로 사용하기 위함이라고...
이제 여기서 유니티 개발자라면 UniTask를 써봤을 것이고, UnitaskVoid의 함수를 task.forget(); 형식으로 써봤을 것이다.
그러면 Unitask에서는 fire & forget을 어떻게 위의 문제를 야기하지 않고 해결 했을까
UniTaskVoid 클래스를 살펴보면, UniTask만의 custom async method builder가 있는 것을 확인 할수가 있다. UniTaskVoid는 AsyncUniTaskVoidMethodBuilder를 사용하는데,
이때 UniTaskScheduler에 UnobservedTask를 등록하여,
에러가 난다면 Unitask schelduler에서 걸리게 되게 한다.
비교하자면, async/void 에서 에러가 난다면 try/catch문에서 '빠져나올수' 있게 된다. 하지만, UniTaskVoid의 경우 Unity game loop를 사용하기 때문에, 빠져나가지 않는다.
'Programming > C#' 카테고리의 다른 글
C# - 부동 소수점 오차 (0) | 2023.08.20 |
---|---|
c++ -> c#인터프리터 정리 (0) | 2023.07.03 |
Effective C# - item 1 ~ 10 (0) | 2022.10.03 |
Effective C# - 제네릭의 활용) 타입 매겨변수가 IDispoable을 구현할 경우를 대비해 제네릭을 만든다. (0) | 2022.09.11 |
Effective C# - 제네릭의 활용) 런타임에 타입을 확인하여 최적의 알고리즘을 사용하자. (0) | 2022.09.11 |