Coroutine

[Coroutine Basics 3] Coroutine Builder and Job

xiaolin219 2024. 1. 18. 17:39
  • Coroutine Builder : 코루틴을 만드는 함수

1. runBlocking

  • runBloking으로 인해 만들어진 코루틴과, 그 내부에 있는 코로틴이 모두 완료 될 때까지 스레드 블락
  • 프로그램 최초 진입 지점인 메인함수, 혹은 테스트 코드 시작 시에만 사용 권장
  • 반환값 없음

2. launch

  • launch의 반환 값은 Job 객체이다

  • Job 객체를 활용해 코루틴을 제어할 수 있다

    • start() : launch{} 블록에서 CoroutineStart.LAZY 옵션을 사용 후 start()를 통해 동작

    • cancel() : 코루틴 취소

    • join() : 코루틴이 끝날때 까지 대기

      fun main(): Unit = runBlocking {
        val job1 = launch {
          delay(1_000)
          printWithThread("Job 1")
        }
        job1.join() // 해당 함수의 유무에 따라
        val job2 = launch {
          delay(1_000)
          printWithThread("Job 2")
          } 
      }
      join() 없을 때 join() 있을 때
      image-20240118163521641 image-20240118163809926
      실행시간 1.1초 실행시간 2초

3. async

launch와 async

  • launch와 유사하나, 실행결과를 반환 받을 수 있다

    fun main(): Unit = runBlocking {
      val job = async {
            3+5 
      }
      val eight = job.await()
    }
  • async()는 Defferd 객체를 반환한다

  • launch의 반환 타입인 Job과 Deffered는 둘 다 인터페이스이다. 아래 두번째 이미지와 같이 Deffered는 Job을 상속받고 있다.

  • 따라서 launch에 기재해둔 함수를 동일하게 사용할 수 있으나, await() 함수를 통해 실행 결과 값 반환이 가능하다

  • async를 활용해 여러 api를 동시에 호출하여 소요시간을 최소화 할 수 있다

    async 블록 내부 Deffered와 Job의 관계
    image-20240118165646002 image-20240118165702322

async와 await

  • 아래와 같이 async 블록을 통해 두개의 api를 호출하는 예시를 보자.

  • 아래 코드에서 비동기로 각각 두개의 api가 호출됨으로 소요시간을 최소화 할 수 있다.

  • async()와 await() 함수 활용을 통해 동기식으로 코드 작성이 가능하다 (callback x)

    // apiCall1 : 1초 소요, apiCall2 : 1초 소요 가정
    
    // api1, 2가 동시에 호출되
    // 총 1.013 초 소요
    fun main(): Unit = runBlocking {
      val time = measureTimeMillis {
        val job1 = async { apiCall1() }
        val job2 = async { apiCall2() }
        printWithThread(job1.await() + job2.await())
      }
    }
    
    // 만약 apiCall2에서 apiCall1의 결과값이 필요한경우
    // job1의 결과 도착 후 apiCall2 실행됨
    // 총 약 2초 소요
    suspend fun apiCall2(num: Int): Int {
      delay(1_000L)
      return num + 2
    }
    
    fun main(): Unit = runBlocking {
      val time = measureTimeMillis {
        val job1 = async { apiCall1() }
        val job2 = async { apiCall2(job1.await()) } // job1의 결과 도착 후 apiCall2 실행
        printWithThread(job2.await())
      }
    }
    
    // Lazy 옵션을 사용했는데, start()를 명시적으로 사용하지 않는다면
    // await()를 만났을 때 코드가 실행되기 때문에 두개의 api가 동시에 호출되지 않음
    // 총 약 2초 소요
    fun main(): Unit = runBlocking {
      val time = measureTimeMillis {
        val job1 = async(start = CoroutineStart.LAZY) { apiCall1() }
        val job2 = async(start = CoroutineStart.LAZY) { apiCall2() }
        printWithThread(job1.await() + job2.await())
      }
    }
    
    // start를 명시적으로 사용했기에 동시 호출
    // 총 약 1초 소요
    fun main(): Unit = runBlocking {
      val time = measureTimeMillis {
        val job1 = async(start = CoroutineStart.LAZY) { apiCall1() }
        val job2 = async(start = CoroutineStart.LAZY) { apiCall2() }
        job1.start()
        job2.start()
        printWithThread(job1.await() + job2.await())
      }
    }