본문 바로가기

Language/Kotlin

[Kotlin] 코루틴을 통한 비동기 처리

728x90
반응형

여러 개의 루틴을 동시에 실행하여 결과를 내고 싶다면 비동기처리를 지원하는 코루틴을 사용한다. 코루틴은 메인루틴과 별도로 진행이 가능한 루틴으로 개발자가 루틴의 실행과 종료를 마음대로 제어할 수 있는 단위이다.

 

코루틴을 사용할 때는 코루틴 extensions의 패키지를 모두 import 해야한다.

import kotlinx.coroutines.*

코루틴의 Scope

코루틴은 제어범위 및 실행범위를 지정할 수 있다.

  • GlobalScope : 프로그램 어디서나 제어, 동작이 가능한 기본 범위
  • CoroutineScope : 특정한 목적의 Dispatcher를 지정하여 제어 및 동작이 가능한 범위

CoroutineScope를 만들 때 적용가능한 Dispatcher

  • Dispatchers.Default : 기본적인 백그라운드 동작
  • Dispatchers.IO : I/O에 최적화된 동작
  • Dispatchers.Main : 메인(UI) 스레드에서 동작

이런 Dispatcher들은 모든 플랫폼에서 지원되지 않으니 지원되는 플랫폼에 따라서 사용해야 한다.

코루틴은 이러한 Scope에서 제어되도록 생성될 수 있다.

// scope 설정
val scope = CoroutineScope(Dispatcher.Default)

// 코루틴 생성
val coroutineA = scope.launch{}
val coroutineB = scope.async{}

 

  • launch : 반환값이 없는 Job 객체 반환
  • async : 반환값이 있는 Deferred 객체 반환

launch, async 모두 람다 함수 형태를 가지고 있으며 async는 마지막 구문의 실행 결과가 반환된다.

launch{
    for(i in 1..10){
        println(i)
    }
}

async{
    var sum = 0
    for(i in 1..10){
        sum++
    }
    sum  // 이 값이 반환된다!!
}

 

import kotlinx.coroutines.*

fun main(){
    val scope = GlobalScope

    scope.launch{
        for(i in 1..5){
            println(i)
        }
    }
}

// 이 코드를 실행하면 아무것도 출력되지 않는다!

코루틴은 제어되는 스코프 또는 프로그램 전체가 종료되면 함께 종료되기 때문에 코루틴이 끝까지 실행되는 것을 보장하려면 일정한 범위에서 코루틴이 모두 실행될 때까지 잠시 기다려주어야 한다.

위에서 테스트하는 루틴의 경우 main() 함수 단 하나이기 때문에 프로세스가 거의 ‘실행 즉시 종료’ 되므로 코루틴도 동작되지 못한 것이다.

 

이럴 때는 runBlocking 블록 안에서 코루틴을 생성하면 코루틴이 종료될 때까지 메인 루틴을 잠시 대기시켜 준다.

runBlocking{
    launch{ }
    async{ }
}

주의할 점은 안드로이드에서는 메인 스레드에서 runBlocking을 걸어주면 일정 시간 이상 응답이 없는 경우 ANR(Application Not Responding : 응답 없음 오류)이 발생하며 앱이 강제 종료된다.

 

import kotlinx.coroutines.*

fun main(){
    runBlocking{
        launch{
            for(i in 1..5){
                println(i)
            }
        }
    }
}

// 출력 :
// 1
// 2
// 3
// 4
// 5

 

 

루틴의 대기를 위한 추가적인 함수들

  • delay(milisecond: Long) : milisecond 단위로 루틴을 잠시 대기시키는 함수
  • Job.join() : Job의 실행이 끝날 때까지 대기하는 함수
  • Deferred.await() : Deferred의 실행이 끝날 때까지 대기하는 함수. Deferred의 결과도 반환

세 함수들은 코루틴 내부 또는 runBlocking{ }과 같은 루틴의 대기가 가능한 구문 안에서만 동작이 가능하다.

import kotlinx.coroutines.*

fun main(){
    runBlocking{
        val a = launch{
            for(i in 1..5){
                println(i)
                delay(10)
            }
        }

        val b = async{
            "async 종료"
        } 

        println("async 대기")
        println(b.await())

        println("launch 대기")
        a.join()
        println("launch 종료")
    }
}

// 출력 :
// async 대기
// 1
// async 종료
// launch 대기
// 2
// 3
// 4
// 5
// launch 종료

 

 

cancel() 함수 : 코루틴 실행 도중에 중단

코루틴에 cancel()을 걸어주면 다음 두 가지 조건이 발생하며 코루틴을 중단시킬 수 있다.

1. 코루틴 내부의 delay() 함수 또는 yield() 함수가 사용된 위치까지 수행된 뒤 종료됨

2. cancel()로 인해 속성인 isActive가 false가 되므로 이를 확인하여 수동으로 종료함

import kotlinx.coroutines.*

fun main(){
    runBlocking{
        val a = launch{
            for(i in 1..5){
                println(i)
                delay(10)
            }
        }

        val b = async{
            "async 종료"
        } 

        println("async 대기")
        println(b.await())

        println("launch 취소")
        a.cancel()
        println("launch 종료")
    }
}

// 출력 :
// async 대기
// 1
// async 종료
// launch 취소
// launch 종료

 

 

withTimeoutOrNull() 함수 : 제한시간(milisecond 단위) 내에 수행되면 결과값을, 아닌 경우 null을 반환

이 함수도 join()이나 await()처럼 blocking 함수이다.

withTimeoutOrNull(50){
    for(i in 1..1000){
        println(i)
        delay(10)
    }
    "Finish"
}

 

import kotlinx.coroutines.*

fun main(){
    runBlocking{
        val result = withTimeoutOrNull(50){
            for(i in 1..10){
                println(i)
                delay(10)
            }
            "Finish"
        }

        println(result)
    }
}

// 출력 :
// 1
// 2
// 3
// null

 

728x90
반응형

'Language > Kotlin' 카테고리의 다른 글

[Kotlin] 비트연산  (0) 2023.09.25
[Kotlin] 변수의 고급 기술 (상수, lateinit, lazy)  (0) 2023.09.25
[Kotlin] 컬렉션 함수 (2)  (0) 2023.09.24
[Kotlin] 컬렉션 함수 (1)  (0) 2023.09.22
[Kotlin] 컬렉션(2) Set과 Map  (0) 2023.09.22