기록

[코틀린/Kotlin] 코루틴을 통한 비동기 처리 본문

[Study]/Kotlin

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

Dannnnnn 2022. 5. 11. 02:36
반응형

'비동기'로 여러개의 루틴을 동시에 처리하는 법을 알아보자.

 

'여러 개의 루틴'을 동시에 실행하여 결과를 내고싶다면 어떻게 해야할까?

비동기 처리를 지원하는 코루틴을 사용하게 된다.

 

코루틴은 메인이 되는 루틴과 별도로 진행이 가능한 루틴으로, 개발자가 루틴의 실행과 종료를 마음대로 제어할 수 있는 단위이다.

 

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

import kotlinx.coroutines.*

 

코루틴은 제어범위 및 실행범위를 지정할 수 있다. 이를 코루틴의 Scope 라고 한다.

기본적으로 GlobalScope와  CoroutineScope를 지원한다.

 

GlobalScope

프로그램의 어디서나 제어, 동작이 가능한 코루틴의 기본 범위

CoroutineScope

특정한 목적의 Dispatcher를 지정하여 제어 및 동작이 가능한 코루틴의 새로운 범위

 

CoroutineScope에 사용할 수 있는 Dispatcher

Dispatchers.Default : 기본적인 백그라운드에서 동작하는 디스패처

Dispatchers.IO : 네트워크나 디스크 등 I/O에 최적화 된 디스패처

Dispatchers.MAIN : 메인 스레드에서 함께 동작하는 디스패처

 

이러한 디스패처들은 모든 플랫폼에서 지원되지는 않으니 지원되는 플랫폼에 따라서 사용해야 한다.

 

코루틴은 이러한 Scope에서 제어되도록 생성할 수 있는데, 생성된 Scope에서 launch나 async라는 함수를 통해 새로운 코루틴을 생성할 수 있다.

launch와 async의 차이는 생성한 코루틴에서 반환값이 있는지의 여부이다.

launch : 반환값이 없는 코루틴 객체를 생성한다. (Job 객체)

async : 반환값이 있는 코루틴 객체를 생성한다. (Deffered 객체)

 

둘 다 람다함수 형태를 가지고 있으며, 그렇기에 async는 마지막 구문의 실행결과가 반환된다.

import kotlinx.coroutines.*

fun main() {
    
    val scope = GlobalScope

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

실행하면 결과가 나오지 않는다.

코루틴은 제어되는 스코프 또는 프로그램 전체가 종료되면 함께 종료되기 때문이다.

코루틴이 끝까지 실행되는 것을 보장하려면 일정한 범위에서 코루틴이 모두 실행될 때까지 잠시 기다려야 한다.

 

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

 

이럴 때는 runBlocking 블록을 만들고 launch나 async를 직접 생성하면 코루틴이 종료될 때까지 메인 루틴을 잠시 대기시켜 준다.

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

(*application Not Responding: 응답 없음 오류) 

import kotlinx.coroutines.*

fun main() {

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

 

이번엔 끝까지 출력된다.

 

루틴의 대기를 위한 추가적인 함수들도 있다.

delay(), join(), await() 이다.

 

delay(milisecond: Long)

milisecond 단위로 루틴을 잠시 대기시키는 함수

 

Job.join()

Job 객체에서 호출하여 해당 Job 객체의 실행(루틴)이 끝날 때까지 대기하는 함수

 

Deferred.await()

Deferred 객체에서 호출하여 해당 Deferred 객체의 실행(루틴)이 끝날 때까지 대기하는 함수

await() 은 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의 결과를 기다린 후 await() 함수에서 결과를 받아 출력하고, 다시 launch가 끝까지 수행되기를 기다린 후 launch가 종료되었음을 출력한다.

 

코루틴 실행 도중에 중단하는 방법

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

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

2. cancle()로 인해 코루틴 내부에서 속성값인 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 종료")
   }
    
}

코루틴의 작업이 중간에 취소되어 숫자가 끊긴다.

 

withTimeoutOrNull()

제한시간 내에 수행되면 결과값을, 아닌 경우 null 을 반환하는 함수이다.

 

괄호 안에 밀리세컨드 단위의 타임아웃 시간을 정해두고, 중괄호 안에 코루틴 구문들을 만든 후 그 결과값을 받는 형태로 사용한다.

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

import kotlinx.coroutines.*

fun main() {

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

타임아웃 내에 수행할 수 없어 null이 출력됨을 볼 수 있다.

 

 

 

 

 

https://www.youtube.com/watch?v=q7GV68jKLVM&list=PLQdnHjXZyYadiw5aV3p6DwUdXV2bZuhlN&index=31

위 강의를 보면서 개인적으로 공부한 내용을 정리하였습니다.

반응형