# 2장 코루틴 인 액션

## 스레드 생성

코틀린은 스레드 생성 과정을 단순화해서 쉽고 간단하게 스레드를 생성할 수 있다. 지금은 단일 스레드만으로도 충분하지만, 이후 과정에서는 CPU 바운드와 I/O 바운드 작업을 모두 효율적으로 수행하기 위해 스레드 풀도 생성할 것이다.

### CoroutineDispatcher

코틀린에서는 스레드와 스레드 풀을 쉽게 만들 수 있지만 직접 엑세스하거나 제어하지 않는다는 점을 알아야 한다.

여기에서는 스레드를 하나만 갖는 CoroutineDispatcher를 생성할 것이며, 거기에 추가하는 모든 코루틴은 그 특정 스레드에서 실행된다. 그렇게 하려면 단 하나의 스레드만 갖는 CoroutineDispatcher를 확장한 ThreadPoolDispatcher를 생성한다.

```kotlin
fun main(args: Array<String>) = runBlocking {
    val netDispatcher = newSingleThreadContext(name = "ServiceCall")

    val task = GlobalScope.launch(netDispatcher) {
        printCurrentThread()
    }

    task.join()
}
```

### 디스패처에 코루틴 붙이기

디스패처가 만들어졌고 이제 이 디스패처를 사용하는 코루틴을 시작할 수 있다.

#### async 코루틴 시작

결과 처리를 위한 목적으로 코루틴을 시작했다면 async()를 사용해야 한다. async()는 디퍼드 코루틴 프레임워크에서 제공하는 취소 불가능한 넌 블로킹 퓨처(non-blocking cancellable future)를 의미하여, T는 그 결과의 유형을 나타낸다.

```kotlin
fun main(args: Array<String>) = runBlocking {
    val task = GlobalScope.async {
        doSomething()
    }
		task.join()
		println("Completed")
}

fun doSomething() {
    throw UnsupportedOperationException("Can't do")
}
```

예외를 통해 애플리케이션 실행이 멈추고 예외 스택이 출력되며 또한 애플리케이션의 종료가 되지 아닐거라고 생각할 수 있다. 그러나 실행해보면 로그에 출력되는 예외 스택은 없으며 애플리케이션도 중단되지 않았고 종료 코드는 성공적으로 실행된 것으로 나타난다.

async() 블록 안에서 발생하는 예외는 그 결과에 첨부되는데, 그 결과를 확인해야 예외를 찾을 수 있다. 이를 위해서 isCancelled와 getCancellationException() 메소드를 함께 사용해 안전하게 예외를 가져올 수 있다.

```kotlin
if (task.isCancelled) {
    val exception = task.getCancellationException()
    println("Error with message: ${exception.cause}")
} else {
    println("Success")
}
```

예외를 전파하기 위해서 디퍼드에서 await()을 호출할 수 있다.

```kotlin
fun main(args: Array<String>) = runBlocking {
    val task = GlobalScope.async {
        doSomething()
    }

    // This code will have the exception be propagated
    task.await()
		println("Success")
}
```

그러면 애플리케이션이 비정상적으로 중단된다.

await()를 호출해서 중단되는데 이 경우가 예외를 감싸지 않고 전파하는 감싸지 않은 디퍼드(unwrapping deferred)다.

join()으로 대기한 후 검증하고 어떤 오류를 처리하는 것과 await()를 직접 호출하는 방식의 주요 차이는 join()은 예외를 전파하지 않고 처리하는 반면, await()는 단지 호출하는 것만으로 예외가 전파된다는 점이다.

### launch 코루틴 시작

결과를 반환하지 않는 코루틴을 시작하려면 launch()를 사용해야 한다. launch()는 연산이 실패한 경우에만 통보 받기를 원하는 fire-and-forget 시나리오를 위해 설계됐으며, 필요할 때 취소할 수 있는 함수도 함께 제공된다.

```kotlin
fun main(args: Array<String>) = runBlocking {
    val task = GlobalScope.launch {
        doSomething()
    }

    // This code will have the exception be propagated
    task.join()
		println("Success")
}
```

예상한 대로 예외가 스택에 출력되지만 실행이 중단되지 않았고, 애플리케이션은 main()의 실행을 완료했다는 것을 알 수 있다.

### 코루틴을 시작할 때 특정 디스패처 사용하기

지금까지는 기본 디스패처를 사용했지만 다음과 같이 특정 코루틴 디스패처를 사용할 수 있다.

```kotlin
fun main(args: Array<String>) = runBlocking {
		val dispatcher = newSingleThreadContext(name = "ServiceCall")
    val task = GlobalScope.launch(dispatcher) {
        doSomething()
    }

    // This code will have the exception be propagated
    task.join()
		println("Success")
}
```

## 요약

* 네트워크 요청은 백그라운드 스레드에서 수행해야 한다. 업데이트되는 뷰를 위한 정보는 UI 스레드로 전달해야 한다.
* CoroutineDispatcher는 코루틴을 특정 스레드 또는 스레드 그룹에서 실행하도록 할 수 있다.
* 하나 이상의 코루틴을 launch 또는 async로 스레드에서 실행할 수 있다.
* launch는 파이어앤포갯(fire-and-forget\_와 같은 시나리오에서 사용돼야 하는데, 코루틴이 무언가를 반환할 것을 예상하지 않는 경우를 말한다.
* 동시 코드를 작성하는 방법에는 여러 가지가 있지만, 명확하고 안전하며 일관성 있게 코틀린의 유연성을 최대한 활용하는 방법을 이해하는 것이 중요하다.
