본문 바로가기

Coroutines

[Coroutines] Coroutine Basic1 (CoroutineScope, CoroutineContext, Dispatcher, Async, launch)

지금까지 프로젝트를 하면서 네트워크 및 계산처리 등의 비동기 작업을 RxJava / RxKotlin으로 구현해왔었다.

Rx는 데이터의 흐름을 보면서 작업을 할 수 있다는 장점이 있다.

하지만 이 라이브러리를 효율적으로 100%활용하려면 Rx의 마법같은 메서드와 로직들을 많이 알아야했는데, 러닝커브가 상당히 높았다.

 

많은 회사들이 비동기 처리를 더욱 간편하게, 더 가볍게 사용할 수 있는 코루틴으로 대체하고 있다.

Coroutine은 특정 위치에서 일시정지하고 다시 시작할 수 있는 많은 진입점들을 허용한다.

이러한 장점으로 Thread를 차단하지 않고 다른 코루틴을 실행하면서 많은 동시작업을 진행시킬 수 있다.

코루틴의 경량성과 비동기 작업의 쉬운 통제가 강력한 장점인 것 같다.

 

필자 또한 리팩토링을 진행하면서 코루틴으로 변경을 하고 있으면 그 과정에서 공부했던 것들을 raw하게나마 작성해보려고 한다.

 

✅ CoroutineScope

코루틴의 범위, 코루틴 블록을 묶음으로 제어할 수 있는 단위이다. 코루틴을 실행시키고 싶은 lifecycle에 따라 scope를 생성하여 코루틴이 실행될 작업 범위를 지정할 수 있다.

✅ CoroutineContext

코루틴을 어떻게 처리할 것인지에 대한 여러가지 정보의 집합으로 Job과 Dispatcher가 있다.

1) Job: Coroutine을 고유하게 식별하고 제어한다.

2) Dispatcher: Coroutine을 어떤 스레드에서 실행할 것인지에 대한 동작을 지정한다.

  • Dispatchers.Default: CPU 사용량이 많은 작업에 사용한다.
  • Dispatchers.IO: 네트워크, 디스크를 사용할 때 이용한다. 파일을 읽고, 쓰고, 소켓을 사용하고 멈추는 것에 최적화 되어있다.
  • Dispatchers.Main: UI와 상호작용하는 작업을 실행하기 위해서만 사용해야 한다.
// 새로운 CoroutineScope 생성 
CoroutineScope(Dispatchers.Default).launch {

}

//기존의 CoroutineScope 재사용
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {

}

//기존 Scope를 그대로 사용하나 백그라운드에서 작업
scope.launch(Dispatchers.Main) {

}

 

✅ async & launch

같은 Scope 내에서 asynclaunch를 이용하여 여러가지 작업들을 동시에 수행할 수 있다.

두 메서드의 큰 차이점은 return값이다.

간단히 말해서 async는 결과 값을 반환하고 launch는 Job을 반환한다.
async는 값을 Deffered로 한번 감싸서 반환시킨다.

Deffered객체는 await() 이라는 함수를 제공하는데 이를 통해서 결과가 반환될 때까지 기다리고 해당 반환값을 꺼내올 수 있다. await() 함수 또한코루틴 블록 내에서만 실행이 가능하다.

 

1) async & await

val randomNumber = viewModelScope.async{  
  //Deffered<Int> 반환
  (0..50).random()  
}  
randomNumber.await()

 

2) launch

viewModelScope.launch {  
  //Job반환
  (0..50).random()  
}

 

3) Coroutine Job 실행 지연

launch와 async 코루틴 블록은 start 매개변수에 CoroutineStart.LAZY를 선언함으로써 처리 시점을 지연시킬 수 있다.

처리가 지연된 launch는 start() 와 join() 메서드를 통해서,
async는 start() 와 await() 을 이용해서 작업을 시작시킬 수 있다.

 

val job = GlobalScope.async(start = CoroutineStart.LAZY) { function() }
job.start()
//or
job.await()

val job2 = GlobalScope.launch(start = CoroutineStart.LAZY) { function() }
job2.start()
//or
job2.join()

 

지연된 async블록의 경우 start() 메서드는 수행결과를 반환하지 않으며, 블록의 작업이 완료될 때까지 기다리지 않는다.
반면 await()의 경우 수행결과를 반환하면서 작업이 끝날 때까지 기다린다.

 

지연된 launch블록에서 start()메서드는 위에서 설명한 async블록에서 설명했던 start()와 같고, join() 메서드는 코루틴 동작이 완료되기까지 기다린다.

✅ withContext()

코루틴 블록으로부터 결과값을 반환 받으려면 await()메서드를 이용해야 했다.
withContext를 사용해서도 비동기 작업을 순차적으로 진행시킬 수 있다.
혹은 원하는 코루틴Context로 변경하고 싶을 때에도 사용할 수 있다.

• withContext의 특성

  1. 블록의 마지막 줄을 반환한다.
  2. 블록이 끝나기 전까지 해당 코루틴을 일시정지 시킨다.
suspend fun getRandomInt() {  
  val randomNumber = withContext(Dispatchers.IO) {  
      //Int값 반환
      (0..50).random() 
  }
  println("$randomNumber")
}

 

'Coroutines' 카테고리의 다른 글

[Coroutines] Channel, Pipeline  (0) 2022.02.10
[Coroutines] StateFlow  (0) 2022.02.09
[Coroutines] Flow  (0) 2022.02.09
[Coroutines] Job (status, cancel error handling)  (0) 2022.02.08