-
📕 코틀린 동시성 프로그래밍 - Ch.2) Coroutine in ActionKOTLIN 2022. 11. 11. 15:29
스레드 생성
코틀린에서는 직관적인 프로세스로 손쉽게 스레드를 생성할 수 있다. 현재 챕터에서는 하나의 스레드로 충분하지만, 다른 챕터에서 CPU-bound, I/O-bound 작업을 효율적으로 실행하기 위한 스레드 풀을 생성한다.
CoroutineDispatcher
코루틴에서 스레드는 손쉽게 생성할 수 있지만, 스레드에 직접적으로 접근하거나 통제하는 것은 불가능하다. 스레드의 가용성, 부하, 구성에 따라 코루틴을 분배하는 작업은 CoroutineDispatcher가 통제하여 수행한다. 예를 들어 ThreadPoolDispatcher를 사용하여 하나의 스레드만을 이용하여 코루틴이 실행되도록 설정할 수 있다.
코루틴에 Dispatcher를 지정하기
Dispatcher가 존재한다면 Dispatcher를 사용하는 코루틴을 쉽게 시작할 수 있다. 코루틴은 실행하는 방법은 2가지가 존재하는데 다음과 같다.
1) async와 함께 코루틴 시작하기
코루틴이 결과 값을 가지는 프로세스를 가지도록 시작하였다면 async()가 사용되어야 한다. async()는 Deferred<T> 타입의 리턴을 갖는데 Deferred는 코루틴 프레임워크에 의해서 제공되는 취소 가능한 non-blocking 미래로 T는 결과값의 타입을 나타낸다. 따라서, async로 처리할 때는 결과값을 처리하는 것을 잊지 말아야 한다. 추가로, 코루틴 빌더를 사용하기 위해서는 CoroutineScope를 사용하여 범위를 지정해주어야 한다.
fun main(args: Array<String>) = runBlocking { val task = GlobalScope.async { doSomething() } task.join() println("Completed") } fun doSomething() { throw UnsupportedOperationException("Can't do") }
여기서 우리는 doSomething() 메서드가 작동되어 UnsupportedOperationException이 발생할 것이라 예측하겠지만 사실, 위의 코드를 실행하면 "Completed" 메세지가 출력된다. async()블럭 안에서 발생하는 예외는 결과값으로 지정되기 때문에 이를 체크해야지만 예외를 찾을 수 있다.
fun main(args: Array<String>) = runBlocking { val task = GlobalScope.async { doSomething() } task.join() if (task.isCancelled) { val exception = task.getCancellationException() println("Error with message: ${exception.message}") } else { println("Success") } }
예외를 안전하게 발생시키기위해서 isCancelled(최근 -> isCompletedExceptionally)와 getCancellationException() 메서드가 함께 사용될 수 있다.
fun main(args: Array<String>) = runBlocking { val task = async { doSomething() } task.await() println("Completed") }
예외를 처리하지 않고 그대로 전파하기 위해서는 await()를 사용하면 Deferred 타입의 반환값을 사용할 수 있다. await()는 Deferred 타입을 unwrapping 처리하기 때문에 예외를 꺼내서 전파한다. join()은 예외를 전파하지않고 처리하는 반면에 await()은 예외를 전파시킨다. 그렇기 때문에 await()의 경우 실행중 에러가 발생했음을 나타내는 예외(code 1)를 반환하지만 join()의 경우에는 isCancelled, getCancellationException()을 사용하여 에러를 처리할 수 있도록 반환하여 예외가 발생하지 않고 성공(code 0)을 반환한다. 챕터 3에서 적절한 예외 처리와 예상되는 행동에 따른 예외 전파를 배운다.
2) launch와 함께 코루틴 시작하기
만약 코루틴이 결과값을 반환하지 않는다고 가정하고 코루틴을 시작한다면 launch()를 사용해야 한다.launch()는 fire-and-forget 시나리오를 목적으로 계산이 실패하였을 때 알려주기를 바라거나 필요한경우 실행을 취소하기 위한 함수를 제공하기 위해 만들어졌다.
fun main(args: Array<String>) = runBlocking { val task = GlobalScope.launch { doSomething() } task.join() println("Completed") } fun doSomething() { throw UnsupportedOperationException("Can't do") }
위의 코드를 실행해보면 예외가 발생하기는 하지만 프로그램의 실행이 방해받지 않고 완료되어 "Completed"도 함께 출력되는 것을 확인할 수 있다.
코루틴을 시작할 때 특정 dispatcher를 사용하기
위의 async()와 launch()의 경우 모두 default dispatcher를 이용하여 코루틴을 시작하였다. 다음과 같이 dispatcher를 지정하여 코루틴을 시작할 수도 있다.
fun main(args: Array<String>) = runBlocking { val dispatcher = newSingleThreadContext(name = "ServiceCall") val task = launch(dispatcher) { printCurrentThread() } task.join() }
결과적으로 "Running in thread [ServiceCall]"이 출력되어 지정된 dispatcher를 이용하여 스레드가 실행되었음을 확인할 수 있다.
네트워크 사용 허가 추가하기
안드로이드에서는 사용자가 특정 허가를 거부하거나 예상과는 다른 행동을 행하는 것을 방지하기 위해서 다양한 기능들에 접근하기 위한 허가 요청이 필요하다.
방식 선택하기
- 코루틴 안의 sync 함수
- 명확하지만 장황함
- 특정 dispatcher가 지정된 async 함수
- 덜 복잡하지만 caller가 어떤 dispatcher를 사용할지 결정할 수 없어 유연하지 못함
- async 함수를 명시적으로 정의하는 것은 개발자의 몫으로 의존적임
- flexible dispatcher와 async 함수
- caller가 어디서 코루틴을 실행할 지 결정할 수 있지만 여전히 개발자가 함수에 적절한 의미를 붙여야 함
따라서 상황에 맞는 방식을 선택해야 한다.
상황에 맞는 선택 예시
- platform 제한이 필요한 경우
- 예를들어 안드로이드의 경우 네트워크 요청을 UI 스레드에서 처리할 수 없는데, 이런 경우에 코드가 네트워킹을 시행하는 시점에 잘못된 스레드를 부르는 것을 피하게 하기 위해서 async 함수를 사용할 수 있다.
- 함수가 여러 곳에서 불려지는 경우
- 중복되는 부분을 sync 함수를 launch()나 async()블럭으로 묶는 것도 괜찮다. 동시성 이슈가 발생할 수 있지만 여러 클래스에 중복 되는 부분을 작성하는 것 보다 async 함수 안에 넣는 것이 더 코드를 읽기 쉽게 만든다.
- 어떤 dispatcher를 사용할 지 caller가 결정하도록 하도록하고 싶은지
- 몇가지 상황에서는 특정 dispatcher에서 코드가 수행되어야 하는데, 예를 들어 원자성 침해를 방지하기 위해서는 caller가 어떤 dispatcher를 원하는지에 관계없이 async()는 특정 dispatcher와 함께 수행되어야 한다.
- 이름의 정확성을 보장할 수 있는지
- async() 함수임을 명시할 수 없는 경우 사용을 피해야 한다.
하나의 함수에서 async(), sync()를 모두 제공할 필요는 없다. 추가적으로 같은 프로젝트 안에 무거운 여러 접근을 섞는 것을 피해라. 코드 전반에 걸쳐서 일정하게 유지하기 위해서 하나를 고수해야 코드를 읽기 쉽고 여러 버그로 부터 방지할 수 있다.
'KOTLIN' 카테고리의 다른 글
코틀린에서 Data클래스에 JPA를 사용할 때 주의해야 할 점 (0) 2022.11.27 코틀린에서 테스트 하기 (⏳) (0) 2022.11.27 📕 코틀린 동시성 프로그래밍 - Ch.1) Hello, Concurrent World! (0) 2022.11.08 코프링(코틀린 + 스프링부트) + 구글 스프레드 시트로 슬랙봇 만들기 - ④ 구글 스프레드 시트 사용하기 (0) 2022.10.22 코프링(코틀린 + 스프링부트) + 구글 스프레드 시트로 슬랙봇 만들기 - ③ 슬랙으로 메세지, view 보내기 (0) 2022.10.22 - 코루틴 안의 sync 함수