•
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
복사