1.
Coroutines: First things first
- CoroutineScope : launch나 async로 실행행된 코루틴을 참조. Job이 있는 경우 scope.cancel()을 통해 코루틴을 취소할 수 있다. 이때 관련된 모든 코루틴들(children)이 취소된다.
- Job : 코루틴에의 핸들. 코루틴을 생성하면 리턴되는 값. 코루틴의 lifecycle을 관리한다. 특정 코루틴만 취소하고 싶은 경우에는 코루틴이 리턴하는 Job의 cancel()을 통해 그 코루틴만 취소할 수 있다.
- CoroutineContext : Job, CoroutineDispatcher, CoroutineName, CoroutineExceptionHandler를 통해 코루틴의 행동을 정의하는 context로서 Job을 통해 코루틴의 lifecycle을 관리하고 CoroutineDispatcher를 통해 어떤 쓰레드에서 작업을 실행할지 지정하고 CoroutineName은 코루틴의 이름을 의미하고 CoroutineExceptionHandler를 통해 uncaught exception을 처리한다.
- Job lifecycle : 정상적인 경우 다음의 상태를 가진다. New -> Active -> Completing -> Completed. Active나 Completing에서 cancel이나 fail이 발생하면 Cancelling 상태로 변경되고, 이 Job과 관련된 모든 코루틴들의 작업이 끝나면 Cancelled로 변경된다.
- Parent context = Defaults + inherited CoutineContext + arguments
(Defaults : CoroutineDispatcher=Dispatchers.Default, CoroutineName="coroutine")
- New coroutine context = parent CoroutineContext + Job()
2.
Cancellation in coroutines
- Job을 취소할 때 사용하는 함수인 cancel()에는 parameter로 CancellationException을 받을 수 있다. 취소에 대한 좀 더 자세한 정보를 알리고 싶을 때 사용한다.
Job이 취소되면 job은 자신의 parent에게 exception을 알린다. parent는 이 exception이 CancellationException이면 무시한다.
cancel() 함수를 호출한다고 해서 코루틴이 하던 작업이 바로 취소되는 것은 아니다.(cooperative) 따라서, 작업이 끝날때까지 무한 루프를 도는 코루틴이라면 중간에 취소 여부를 확인해주지 않으면 작업이 끝날때까지 동작하게 된다.
- 중간에 취소여부를 확인하기위해 다음의 4가지 방법을 사용할 수 있다. suspend function, job.isActive, ensureActive(), yield()
모든 suspend 함수는 cancellable하기 때문에 이 지점에서 취소 여부에 따라 중단될 수 있다.
if 문에서 isActive를 통해 상태를 확인할 수 있다. 이를 편하게 해주는 ensureActive() 함수를 제공하고 있다. yield는 코루틴의 상태가 cancelled나 completed이면 CancellationException을 throow한다.
- 코루틴으로부터 결과를 기다리는 두가지 방법 : Job.join(), Deferred.await()
Job.join() : 코루틴의 작업이 끝날때까지 기다린다.
Deferred.await() : async { ... }의 리턴에 대해 await를 호출하면 async의 작업이 끝날때까지 기다린다. cancell된 deferred에 대해 await를 호출하면 JobCancellatingException을 throw한다.
- 코루틴이 취소되었을 때 특정한 action을 실행하는 방법
- isActive를 통해 작업을 끝내고 clean up을 진행한다.
- 작업이 취소되면 CancellationException이 호출되므로 try catch finally를 사용한다.
=> clean up 할 때 suspend 함수를 사용하면 안된다.(suspend 함수는 cancelled 상태에서는 실행되지 않는다. 굳이 하고 싶으면 withContext(NonCancellable)을 사용한다.)
3.
Exceptions in Coroutines
- Exception 전달 경로
child coroutine에서 exception이 발생하면 이 정보가 parent에게 전달된다. -> parent는 모든 children coroutine을 취소한다. -> exception가 다시 상위(parent의 parent)로 전달된다.
- 모든 children이 취소되지 않고 나 자신만 취소되게 하려면 SupervisorJob을 사용한다. 이 경우 취소된 child coroutine만 취소되고 다른 child들은 취소되지 않는다.
- Exception 처리하기
-- launch : exception이 발생하면 바로 throw된다. 따라서, 코드에서 try catch를 사용한다.
-- async
--- root coroutine일 경우에는 exception이 바로 throw 되지 않고 나중에 await를 실행할 때 throw 된다. 따라서, await() 주위에서 try catch를 사용한다. 또한 이때 SupervisorJob을 사용행야 한다. Job을 사용하면 exception이 parent로 전달되어 catch가 동작하지 않게 된다.
--- 다른 코루틴 안에서 async가 실행될 경우에는 exception이 바로 throw 된다.
- CoroutineExceptionHandler의 실행 조건(둘 다 성립해야 한다.)
-- exception을 throw하는 코루틴에서 실행된다.(work with launch, not async)
-- root coroutine이거나 CoroutineScope의 CoroutineContext안에 있는 경우
4.
Coroutines & Patterns for work that should't be cancelled
- Coroutine vs WorkManager
process가 있는 동안만 동작해야 하는 경우에는 Coroutine을 사용하고 process와 상관없이 background에서 동작해야 하는 경우에는 WorkManager를 사용한다.
- 취소되지 않아야 하는 작업의 경우를 위해 Application 레벨에서의 CoroutineScope을 만들어서 사용한다.
- GlobalScope, ProcessLifecycleOwner, NonCancellable은 사용하지 않는다.