> For the complete documentation index, see [llms.txt](https://incheol-jung.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://incheol-jung.gitbook.io/docs/study/undefined-4/4.md).

# 4장 일시 중단 함수와 코루틴 컨텍스트

## 일시 중단 함수

이름이나 아이디를 기준으로 프로파일을 검색하는 클라이언트 인터페이스를 설계해보자. 비동기 함수를 갖도록 구현하기 위해 Deferred를 반환하도록 하자

```kotlin
interface ProfileServiceRepository {
		fun asyncFetchByName(name: String) : Deferred<Profile>
		fun asyncFetchbyId(id: Long) : Deferred<Profile>
}
```

모의 구현은 간단하다.

```kotlin
class ProfileServiceClient : ProfileServiceRepository {
		override fun asyncFetchByName(name: String) = GlobalScope.async {
				Profile(1, name, 28)
		}
		fun asyncFetchbyId(id: Long) : GlobalScope.async {
				Profile(id, "Susan", 28)
		}
}
```

이 구현을 호출해보자.

```kotlin
fun main(args: Array<String>) = runBlocking {
		val client : ProfileServiceRepository = ProfileServiceClient()
		val profile = client.asyncFetchById(12).await()

		println(profile)
}
```

구현에서 관찰할 수 있는 몇 가지 사항이 있다.

* 함수 이름이 이해하기 편히하게 돼 있다. 필요할 때 클라이언트가 진행하는 것을 완료할 때까지 대기해야 한다는 것을 알 수 있도록, 함수가 비동기(async)라는 점을 명시하는 것이 중요하다.
* 호출자는 항상 요청이 완료될 때까지 일시정지해야 하므로 보통 함수 호출 직후에 await() 호출이 있게 된다.
* 구현은 Deferred와 엮이게 될 것이다. 다른 유형의 퓨처(feture)로 ProfileServiceRepository 인터페이스를 깔끔하게 구현하기 위한 방법은 없다. 코틀린이 아닌 동시성 기본형으로 구현하면 자칫 지저분해질 수 있다.

### 일시 중단 함수로 업그레이드

일시 중단 함수를 사용하기 위해 코드를 리팩토링해보자.

긴 함수 이름 대신 좀더 깔끔한 이름을 가질 수도 있다. 더 중요한 것은 비동기 함수로 구현하도록 강제하는 인터페이스 대신 일시 중단과 Profile를 반환하는 작업에만 신경 쓰면 된다는 것이다. 이제 다음과 같이 Deffered를 제거할 수 있다.

```kotlin
interface ProfileServiceRepository {
		suspend fun fetchByName(name: String) : Profile
		suspend fun fetchById(id: Long) : Profile
}
```

구현 또한 쉽게 바뀔 수 있다.

```kotlin
class ProfileServiceClient : ProfileServiceRepository {
		override suspend fun fetchByName(name: String) : Profile {
				return Profile(1, name, 28)
		}
		override suspend fun fetchById(name: String) : Profile {
				return Profile(id, "Susan", 28)
		}
}
```

이 방식은 비동기 구현에 비해 몇 가지 분명한 이점이 있다.

* 유연함 : 인터페이스의 상세 구현 내용은 노출되지 않기 때문에 퓨처를 지원하는 모든 라이브러리를 구현에서 사용할 수 있다.
* 간단함 : 순차적으로 수행하려는 작업에 비동기 함수를 사용하면 항상 await()를 호출해야 하는 번거로움이 생기고, 명시적으로 async가 포함된 함수의 이름을 지정해야 한다.

### 일시 중단 함수와 비동기 함수

비동기 함수 대신 일시 중단 함수를 사용하기 위한 가이드라인은 다음과 같다.

* 일반적으로 구현에 Job이 엮이는 것을 피하기 위해서는 일시 중단 함수를 사용하는 것이 좋다.
* 인터페이스를 정의할 때는 항상 일시 중단 함수를 사용한다. 비동기 함수를 사용하면 Job을 반환하기 위한 구현을 해야 한다.
* 마찬가지로 추상(abstract) 함수를 정의할 때는 항상 일시 중단 함수를 사용한다. 가시성이 높은 함수일수록 일시 중단 함수를 사용해야 한다.

## 코루틴 컨텍스트

코루틴은 항상 컨텍스트 안에서 실행된다. 컨텍스트는 코루틴이 어떻게 실행되고 동작해야 하는지를 정의할 수 있게 해주는 요소들의 그룹이다.

### 디스패처

디스패처(Dispatcher)는 코루틴이 실행될 스레드를 결정하는데, 여기에는 시작될 곳과 중단 후 재개될 곳을 모두 포함된다.

### CommonPool

CommonPool은 CPU 바운드 작업을 위해서 프레임워크에 의해 자동으로 생성되는 스레드 풀이다. 스레드 풀의 최대 크기는 시스템의 코어 수에서 1을 뺀 값이다. 현재는 기본 디스패처로 사용된다.

### 예외 처리

코루틴 컨텍스트의 또 다른 중요한 용도는 예측이 어려운 예외에 대한 동작을 정의하는 것이다. 이러한 유형의 컨텍스트는 다음과 같이 CoroutineExceptionHandler를 구현해 만들 수 있다.

```kotlin
fun main(args: Array<String>) = runBlocking {
		val handler = CoroutineExceptionHandler({ context, throwable ->
				println("Error captured in $context")
				println("Message: ${throwable.message}")
		})

		GlobalScope.launch(handler) {
				TODO("Not implemented yet!")
		}
}
```

### Non-cancellable

앞에서 다룬 것처럼 코루틴의 실행이 취소되면 코루틴 내부에 CancellationException 유형의 예외가 발생하고 코루틴이 종료된다.

```kotlin
suspend fun nonCancellable() {
    val duration = measureTimeMillis {
        val job = GlobalScope.launch {
            try {
                while (isActive) {
                    delay(500)
                    println("still running")
                }
            } finally {
                withContext(NonCancellable) { // NoneCancellable 컨텍스트를 사용하여 코루틴이 취소 되더라도 중지 되지 않는다. 
                    println("cancelled, will delay finalization now")
                    delay(5000)
                    println("delay completed, bye bye")
                }
            }
        }

        delay(1200)
        job.cancelAndJoin()
    }

    println("Took $duration ms")
}
```

NonCancellable 컨텍스트를 사용하지 않는다면 취소 시 delay가 일어나지 않고 종료되지 않는다. 코루틴이 취소되는 동안 일시 중지가 필요한 경우 NonCancellable 컨텍스트를 사용해야 한다.

## 컨텍스트에 대한 추가 정보

### 컨텍스트 결합

컨텍스트의 일부분이 될 수 있는 여러 종류의 요소가 있다. 다양한 요구사항을 만족하는 컨텍스트를 생성하기 위해 이러한 요소들을 결합시킬 수 있다.

#### 컨텍스트 조합

특정 스레드에서 실행하는 코루틴을 실행하고 동시에 해당 스레드를 위한 예외처리를 설정한다고 가정하자.

```kotlin
fun main(args: Array<String>) = runBlocking {
    val dispatcher = newSingleThreadContext("myDispatcher")
    val handler = CoroutineExceptionHandler { _, throwable ->
        println("Error captured")
        println("Message: ${throwable.message}")
    }

    // Combine two contexts together
    val context = dispatcher + handler

    GlobalScope.launch(context) {
        println("Running in ${Thread.currentThread().name}")
        TODO("Not implemented!")
    }.join()
}
```

#### 컨텍스트 분리

결합된 컨텍스트에서 컨텍스트 요소를 제거할 수도 있다.

```kotlin
fun main(args: Array<String>) = runBlocking {
    val dispatcher = newSingleThreadContext("myDispatcher")
    val handler = CoroutineExceptionHandler { _, throwable ->
        println("Error captured")
        println("Message: ${throwable.message}")
    }

    // Combine two contexts together
    val context = dispatcher + handler

    // Remove one element from the context
    val tmpCtx = context.minusKey(dispatcher.key)

    GlobalScope.launch(tmpCtx) {
        println("Running in ${Thread.currentThread().name}")
        TODO("Not implemented!")
    }.join()
}
```

## 요약

* withContext() 일시 중단 코드 정의를 위한 유연한 방법을 제공할 수 있다.
* 비동기 함수(job 구현을 반환하는 함수)는 특정 구현을 강요하는 위험을 피하기 위해 withContext() 공개 API의 일부가 돼서는 안 된다고 언급했다.
* 코루틴 컨텍스트에 대한 흥미로운 주제와 작동하는 방법을 이해해야 한다.
* 디스패처를 시작으로 예외 처리와 취소 불가능한 고유한 컨텍스트로 옮겨가는 다양한 유형의 코루틴 컨텍스트가 존재한다.
* 코루틴에서 기대하는 동작을 얻기 위해 많은 컨텍스트를 하나로 결합할 수 있다.
* 요소 중 하나의 키를 제거함으로써 결합된 컨텍스트를 분리할수 있다.
* withContext()는 프로세스에 잡을 포함시키지 않고도 다른 컨텍스트로 전환할 수 있게 해주는 일시 중단 함수다.
