Kotlin ‐ Coroutines 구성 요소와 동작 메커니즘 - dnwls16071/Backend_Summary GitHub Wiki
📚 Structred Concurrency
- 부모 - 자식 관계 코루틴이 한몸처럼 움직이는 것을 Structured Concurrency라고 한다.
- Kotlin 공식문서에서 Structured Concurrency에 대해 다음과 같이 이야기하고 있다.
- Structured Concurrency는 수많은 코루틴이 유실되거나 누수되지 않도록 보장한다.
- Structured Concurrency는 코드 내의 에러가 유실되지 않고 적절히 보고될 수 있도록 보장한다.
- 만약 자식 코루틴에서 예외가 발생할 경우 Structured Concurrecy에 의해 부모 코루틴이 취소되고 부모 코루틴의 다른 자식 코루틴들도 모두 취소된다.
- 자식 코루틴에서 예외가 발생하지 않더라도 부모 코루틴이 취소되면 자식 코루틴들도 모두 취소된다.
- 다만
CancellationException의 경우 정상적인 취소로 간주되기 때문에 부모 코루틴에게 전파되지 않고 부모 코루틴의 다른 자식 코루틴들도 취소시키지 않는다.
📚 CoroutineScope와 CoroutineContext
- CoroutineScope의 주요 역할은 CoroutineContext라는 데이터를 보관하는 것에 있다.
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
- CoroutineContext는 코루틴과 관련된 여러 데이터를 가지고 있다.
- Context 안에 현재 코루틴의 이름도 들어있고 CoroutineExceptionHandler가 있을 수도 있고, 코루틴의 Job 객체 자체도 들어 있으며, CoroutineScope을 만들 때 넣어주었던 CoroutineDispatcher도 있다.
- Dispatcher는 코루틴이 어떤 쓰레드에 배정될지를 관리하는 역할을 한다.
class AsyncLogic {
private val scope = CoroutineScope(Dispatchers.Default)
fun doSomething() {
scope.launch {
// ...
}
}
fun destroy() {
scope.cancel()
}
}
doSomething() 함수를 호출해 비동기 작업을 처리하다가 더 이상 필요가 없어지면 destroy() 함수를 호출해 모든 코루틴을 정리할 수 있다.
- CoroutineContext는 Map과 Set을 섞어 둔 자료구조와 같다.
- CoroutineContext에 저장되는 데이터는 key - value로 이루어져 있고 Set과 비슷하게 동일한 Key를 가진 데이터는 하나만 존재할 수 있다.
- 이 key - value 하나를 Element라 부르고, + 기호를 이용해 각 Element를 합치거나 context를 추가할 수 있다.
- 만약 context에서 Element를 제거하고 싶다면 minusKey 함수를 이용해 제거하는 것도 가능하다.
Dispatcher⭐
- Dispatchers.Default : 가장 기본적인 디스패처. CPU 자원을 많이 쓸 때 권장되며 별다른 설정이 없다면 이게 기본값이 된다.
- Dispatchers.IO : I/O 작업에 최적화된 디스패처.
- Dispatchers.Main : 보통 UI 컴포넌트를 조작하기 위한 디스패처. 특정 의존성을 갖고 있어야 정상적으로 활용할 수 있다.
- Java의 스레드풀인 ExecutorService를 디스패처로 변환 -
asCoroutineDispatcher()이라는 확장함수를 이용해ExecutorService를 디스패처로 전환할 수 있다.
📚 suspending function
- suspending function이란, suspend 키워드가 붙은 함수를 의미한다.
- suspend 키워드가 붙은 함수에서 또 다른 suspend 함수를 호출할 수 있다.
- suspending function는 코루틴이 중지되었다가 다시 재개될 수 있는 시점을 말한다.
- 여러 비동기 라이브러리를 사용할 수 있도록 도와준다.
fun main(): Unit = runBlocking {
launch {
delay(100L)
}
}
- coroutineScope : 추가적인 코루틴을 만들고 주어진 함수 블록이 바로 실행된다. 만들어진 코루틴이 모두 완료되면 다음 코드로 넘어간다.
- withContext : context에 변화를 주는 기능이 추가적으로 있다.
fun main(): Unit = runBlocking {
val result1 = apiCall1()
val result2 = apiCall2(result1)
printWithThread(result2)
}
suspend fun apiCall1(): Int {
return CoroutineScope(Dispatchers.Default).async {
Thread.sleep(1_000L)
}.await()
}
suspend fun apiCall2(num: Int): Int {
return CompletableFuture.supplyAsync {
Thread.sleep(1_000L)
}.await()
}
- apiCall1 함수와 apiCall2 함수를 suspend fun으로 변경해 이 안에서 어떤 비동기 라이브러리 구현체를 사용하건 해당 함수 선택으로 남겨둔다.
- 주어진 시간 안에 코루틴이 완료되지 않으면 withTimeout은 TimeoutCancellationException을 던지게 되고, withTimeoutOrNull은 null을 반환하게 된다.