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을 반환하게 된다.