본문 바로가기
학습 log (이론)/kotlin

'Coroutine' 기본

by abbear25 2019. 12. 15.

Corouties

매우 작고 가벼운 쓰레드 - 수천, 수백개가 동시에 동작해도 no overhead! no crash! 강력함

동기화와 병렬처리를 쉽게 구현가능함

쉬운 표현으로 학습이 쉬움

언제나 일시정지하고 정지시킬 수 있음

CoroutineContext는 coroutine이 실행 중인 여러 job과 dispatcher를 저장하는 일종의 맵

launch, async, runBlocking과 같은 코루틴 빌더가 존재

delay(), yield(), withContext, withTimeout, awaitAll, joinAll 등과 같은 일시중단(suspending) 함수가 존재

 *일시중단 함수는 coroutine이나 일시중단 함수 아닌 곳에서 호출금지

Scope, Context, Suspending functions, Jobs, Deferred, dispatcher, error handling

 

Scope

수명주기 방법을 제공하고 핵심 루틴을 관리

fun main(){
    println("Program execution will now block")
    runBlocking {
        launch {
            delay(1000L)
            println("Task from runBlocking")
        }
        GlobalScope.launch {
            delay(500L)
            println("Task from GlobalScope")
        }
        coroutineScope {
            launch {
                delay(1500L)
                println("Task from coroutineScope")
            }
        }
    }
    println("Program execution will now continue")
}

GlobalScope.launch {}

 전체 응용프로그램 범위의 Coroutine 생성

 전체 응용프로그램이 중지되지 않으면 계속 실행 (프로그램 중지되면 종료), 넓은 범위라 사용빈도 거의 없음

fun main(){
    GlobalScope.launch {
        delay(1000)
        println("HELLO")
    }
    print("HELLO, ")
    //Thread.sleep(5000) main을 coroutine 보다 지연시키지 않으면 coroutine 실행안됨 
}

runBlocking{ }

 scope를 생성 후 coroutine이 종료될 때 까지 다른 스레드 실행을 중지

 일반함수로 coroutine scope없이 사용 가능

fun main(){
    runBlocking {
        launch {
            delay(1000)
            println("HELLO")
        }
    }
    print("HELLO, ")
}

coroutineScope{}

 모든 coroutine이 완료될때까지 완료되지 않는 새로운 scope를 생성할 때 사용

 

Context

coroutine과 관계된 데이터 셋

fun main(){
    runBlocking {
        launch(CoroutineName("myCoroutine")) {
            println("This is run from ${this.coroutineContext.get(CoroutineName.Key)}")
        }
    }
}

context 2가지 요소

 Dispatcher - 각 coroutine을 실행

 Job - coroutine의 생명주기를 조절

 

Suspending functions

coroutine에서 호출 가능한 일시중단 함수로 지정

callback을 위해 사용

var functionCalls = 0

fun main(){
    GlobalScope.launch { completeMessage() }
    GlobalScope.launch { improveMessage() }
    print("begin, ")
    Thread.sleep(2000L)
    println("function Count: $functionCalls")
}

suspend fun completeMessage(){
    delay(500L)
    println("Completed")
    functionCalls++
}

suspend fun improveMessage(){
    delay(1000L)
    println("improve")
    functionCalls++
}

 

Jobs

.launch() 호출을 통해 작업 반환

수명주기를 조작하는 것을 허용

부모 또는 자식 양쪽에 다른 작업의 계층에 존재

기존 수명주기에 변수나 메서드를 취소하거나 추가할 수 있음 - cancel(), join()

작업을 종료하면 모든 부모와  자식이 취소 됨

fun main(){
    runBlocking {
        val job1 = launch {
            //delay(3000L)
            println("job1 start")
            val job2 = launch {
                println("job2 start")
                delay(3000L)
                println("job2 end")
            }
            job2.invokeOnCompletion { println("job2 completed") }
            val job3 = launch {
                println("job3 start")
                delay(3000L)
                println("job3 end")
            }
            job3.invokeOnCompletion { println("job3 completed") }
        }
        job1.invokeOnCompletion {//job1이 종료되는 시점에 동작
            println("job1 completed")
        }
        delay(500L)
        println("job1 cancelled")
        job1.cancel()
    }
}

 

Dispatchers

coroutine이 실행되는 스레드 또는 스레드풀을 결정
구현해야할 경우가 아니면 따로 정의하지 않아도 됨

Main

 UI 업데이트

Default

 데이터 또는 이미지 처리와 같은 CPU 작업

IO

 모든 입출력 작업

Unconfined

 상속받은 Dispatcher에서 coroutine이 호출된 경우

newSingleThreadContext("Thread Name")

 일시적으로 사용할 Thread 생성

fun main(){
    runBlocking {
        /*launch(Dispatchers.Main){
            println("Main: ${Thread.currentThread().name}")
        }*/

        launch(Dispatchers.Unconfined){
            println("Unconfined: ${Thread.currentThread().name}")
            delay(100L)
            println("move Unconfined: ${Thread.currentThread().name}")
        }

        launch(Dispatchers.Default){
            println("Default: ${Thread.currentThread().name}")
            delay(100L)
            println("move Default: ${Thread.currentThread().name}")
        }

        launch(Dispatchers.IO){
            println("IO: ${Thread.currentThread().name}")
        }

        launch(newSingleThreadContext("ThreadName")){
            println("newSingleThreadContext: ${Thread.currentThread().name}")
        }
    }
}

 

Async

지연을 통해서 특정시점에 실행되도록 지정

Deffered를 반환 (.launch()로 실행할 경우 job을 반환)

.await()을 통해 결과가 반환될때 까지 대기 후 반환되면 다음 로직 동작 또는 종료

fun main(){
    runBlocking {
        val firstDeff = async {getFirst() }
        val secondDeff = async {getSecond()  }
        println("processing...")
        delay(500L)
        println("waiting...")
        val firstVal = firstDeff.await()
        val secondVal = secondDeff.await()

        println("result: ${firstVal + secondVal}")
    }
}

suspend fun getFirst(): Int{
    delay(1000L)
    val value = Random.nextInt(100)
    println("FirstVal: $value")
    return value
}

suspend fun getSecond(): Int{
    delay(1000L)
    val value = Random.nextInt(1000)
    println("SecondVal: $value")
    return value
}

 

withContext

context의 전환을 쉽게 하기 위한 기능

화면에서 이미지를 요청, 요청된 이미지를 백그라운드에서 처리 후, 다시 기존 화면에 이미지를 보여주는 작업이 필요할 때

fun main(){
    runBlocking {
        launch(Dispatchers.Default) {
            println("First context: $coroutineContext")
            withContext(Dispatchers.IO){
                println("Second context: $coroutineContext")
            }
            println("Third context: $coroutineContext")
        }
    }
}

 

Exception

coroutine 실행에 따라 예외처리 다름

launch

 부모 자식 관계 상속을 통해서 예외처리

 즉각적인 실패인 경우 예외처리

 try-catch를 이용한 예외처리

async

 결과처리가 완료될 때까지 예외 연기

 만약 결과가 완료되지 않으면 예외는 처리되지 않음

 coroutine 또는 await() 호출될 때 try-catch를 이용한 예외처리 

fun main(){
    runBlocking {
        val myHandler = CoroutineExceptionHandler{ coroutineContext, throwable ->
            println("Exception handler: ${throwable.localizedMessage}")
        }
        val job = GlobalScope.launch(myHandler) {
            println("Throwing exception from job")
            throw IndexOutOfBoundsException("exception!!")
        }
        job.join()

        val deferred = GlobalScope.async {
            println("Exception async")
            throw ArithmeticException("exception!!")
        }
        try {
            deferred.await()
        }catch (e: ArithmeticException){
            println("catch exception: ${e.localizedMessage}")
        }
    }
}

 

반응형

'학습 log (이론) > kotlin' 카테고리의 다른 글

Kotlin기초 'lambda'  (0) 2019.12.07
Kotlin기초 'Closure'  (0) 2019.11.12
Kotlin기초 '유용한 함수'  (0) 2019.11.10
Kotlin기초 '함수형 프로그래밍'  (0) 2019.11.06
Kotlin기초 '객체'  (0) 2019.11.03