소개

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

launch,async 말고 일시 중단 함수(suspending function)을 써보자
일시 중단 함수 개요 및 사용법
일시 중단 함수 대신 비동기 함수를 사용하는 경우
코루틴 컨택스트
dispatcher, exception handlers, non-cancellables 등의 컨택스트
코루틴 동작을 정의하기 위한 컨택스트의 결합 및 분리
안드로이드 ⇒ 피드 갯수만 보여주는게 아니라, 내용 요약까지 보여주도록 하자

일시중단 함수

현재는 launch, async, runBlocking 같은 코루틴 빌더를 이용해서 대부분 작성함
일시중단함수는 suspend만 붙이면 된다.
suspend fun greetDelayed(delayMillis: Int) { delay(delayMillis) println("Hello, World!") }
JavaScript
복사
일시중단 함수가 다른 일시중단 함수를 직접 호출 가능
비 일시중단 함수에서는 컴파일이 안됨
코루틴 빌더로 감싸야 함
fun main(args: Array<String>) { runBlocking { greetDelayed(1000) } }
JavaScript
복사
비동기 함수와 차이점
interface ProfileServiceRepository { fun asyncFetchByNameAsync(name: String) : Deferred<Profile> fun asyncFetchByIdAsync(id: Long) : Deferred<Profile> } class ProfileServiceClient : ProfileServiceRepository { override fun asyncFetchByNameAsync(name: String) = GlobalScope.async { Profile(1, name, 28) } override fun asyncFetchByIdAsync(id: Long) = GlobalScope.async { Profile(id, "Susan", 28) } }
JavaScript
복사
구현이 Deferred와 엮이게 된다
클라이언트에서 항상 await을 호출하게 된다
fun main(args: Array<String>) = runBlocking { val client : ProfileServiceRepository = ProfileServiceClient() val profile = client.asyncFetchByIdAsync(12).await() println(profile) }
JavaScript
복사
suspend로 할때
interface ProfileServiceRepository { suspend fun fetchByName(name: String) : Profile suspend fun fetchById(id: Long) : Profile } class ProfileServiceClient : ProfileServiceRepository { override suspend fun fetchByName(name: String) : Profile { return Profile(1, name, 28) } override suspend fun fetchById(id: Long) : Profile { return Profile(id, "Susan", 28) } } fun main(args: Array<String>) = runBlocking { val repository: ProfileServiceRepository = ProfileServiceClient() val profile = repository.fetchById(12) println(profile) }
JavaScript
복사
가이드
Job과 Deferred와 엮이지 않으려면 일시중단함수를 써라
인터페이스를 정의할때 항상 일시중단함수를 사용
비동기함수로 짜면 Job을 반환하기 위한 구현을 해야됨
추상함수를 정의할때도 일시중단 함수를 써라
가시성이 높을 수록 일시중단을 쓰는게 좋다
Job을 반환하도록 짰다가 deprecated 되면 골치아픔
비동기함수는 private이나 internal로 제한해야됨
Job의 영향범위를 줄인다.

코루틴 컨텍스트

코루틴은 항상 컨택스트 안에서 실행됨
컨택스트 = 코루틴이 어떻게 실행/동작해야 하는지 정의함
디스패처
코루틴이 실행될 스레드를 결정
시작될 곳, 중단후 재개될 곳을 포함
Dispatchers.Default(CommonPool)
자동으로 생성되는 스레드 풀
최대크기는 코어수 - 1
Dispatcher.Unconfined
일시중단을 만나기 전까지 main에서 진행되다가 Resume 한 스레드에서 다시 시작됨
코루틴이 CPU 시간을 소모하지 않거나 공유되는 데이터(UI)를 업데이트 하지 않는 경우처럼 특정 스레드에 국한된 작업이 아닌 경우 적절합니다.
스레드 전환을 위해 나중에 실행되는걸 방지하는 , 일부작업에서 즉시 실행되어야 할 특수한 경우에 사용
단일 스레드 컨택스트
newSingleThreadContext()
항상 코루틴이 특정 스레드 안에서 실행된다는 것을 보장
스레드 풀
newFixedThreadPoolContext()
스레드 풀을 고정된 수로 정해서 운영
다른 분들은 어떻게 설정하시나요?
예외처리
CoroutineExceptionHandler 를 컨택스트에 넘겨줌
Non-cancellable
코루틴 실행이 취소되면 코루틴 내부에서 CancellationException 발생 후 종료
try-finally 블록을 사용해 리소스 클리닝 작업을 수행하거나, 로깅을 수행할 경우가 있음
그런데 정상 동작 안함
취소중인 코루틴은 일시중단될 수 없음 ⇒ finally에서 IO 작업 불가능
일시중단이 필요할때 NonCancellable 컨택스트 써야함
val job = GlobalScope.launch { try { while (isActive) { delay(500) println("still running") } } finally { withContext(NonCancellable) { println("cancelled, will delay finalization now") delay(5000) println("delay completed, bye bye") } } }
JavaScript
복사
컨택스트 결합
조합
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() }
JavaScript
복사
분리
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() }
JavaScript
복사
withContext 사용
async, await을 쓰면 Job 이나 Deferred 를 반환
suspend fun before() { val dispatcher = newSingleThreadContext("myThread") val name = GlobalScope.async(dispatcher) { // Do important operation here "Susan Calvin" }.await() println("User: $name") } suspend fun after() { val dispatcher = newSingleThreadContext("myThread") val name = withContext(dispatcher) { // Do important operation here "Susan Calvin" } println("User: $name") }
JavaScript
복사