Kotlin/코틀린 코루틴의 정석

[Kotlin 코루틴] async와 Deferred

꾸준함. 2024. 11. 15. 16:12

서론

  • launc 코루틴 빌더를 통해 생성되는 코루틴은 기본적으로 작업 실행 후 결과를 반환하지 않지만
  • async 코루틴 빌더를 통해 생성된 코루틴으로부터 결괏값을 수신받을 수 있음
    • async 함수를 사용하면 결괏값이 있는 코루틴 객체인 Deferred가 반환됨
    • Deferred 객체를 통해 코루틴으로부터 결괏값을 수신할 수 있음

 

async 사용해 결괏값 수신하기

 

1. async 사용해 Deferred 생성하기

  • async 함수는 launch 함수와 유사하지만 다음과 같은 차이점이 존재함
    • launch는 코루틴이 결괏값을 직접 반환할 수 없기 때문에 Job 객체를 반환
    • async는 코루틴의 결괏값을 직접 반환하고 결괏값을 담아 반환하기 위해 Deferred<T> 타입의 객체를 반환

 

* Deferred는 Job과 같이 코루틴을 추상화한 객체이지만 코루틴으로부터 생성된 결괏값을 감싸는 기능을 추가로 가지며 결괏값의 타입은 제네릭 타입 T로 표현

 

2. await를 사용한 결괏값 수신

  • Deferred 객체는 미래의 어느 시점에 결괏값이 반환될 수 있음을 표현하는 코루틴 객체
    • 코루틴이 실행 완료될 때 결괏값이 반환됨
    • 만약 결괏값이 필요하다면 결괏값이 수신될 때까지 대기해야 함

 

  • Deferred 객체는 결괏값 수신의 대기를 위해 await 함수 제공
    • await 함수는 await의 대상이 된 Deferred 코루틴이 실행 완료될 때까지 await 함수를 호출한 코루틴을 일시 중단 시킴
    • Deferred 코루틴이 실행 완료되면 결괏값을 반환하고 호출부의 코루틴을 재개함
    • Job 객체의 join 함수와 매우 유사하게 동작


 

Deferred는 특수한 형태의 Job

  • Deferred 객체는 Job 객체의 특수한 형태로 Deferred 인터페이스는 Job 인터페이스의 서브타입으로 선언된 인터페이스
    • 따라서 `모든 코루틴 빌더는 Job 객체를 생성한다`라는 명제는 참
    • Deferred 객체는 코루틴으로부터 결괏값 수신을 위해 Job 객체에 몇 가지 기능이 추가되었을 뿐, 여전히 Job 객체의 일종
    • 이런 특성 때문에 Deferred 객체는 Job 객체의 모든 함수와 프로퍼티를 사용할 수 있음

 

 

복수의 코루틴으로부터 결괏값 수신하기

  • 개발할 때 여러 비동기 작업으로부터 결괏값을 반환받아 병합해야 하는 케이스가 생김
  • 위와 같은 경우에는 복수의 코루틴을 생성해 결괏값을 취합해야 함

 

1. await를 사용해 복수의 코루틴으로부터 결괏값 수신하기

 

 

부연 설명

  • 앞서 언급했다시피 await를 호출하면 결괏값이 반환될 때까지 호출부의 코루틴이 일시 중단됨
  • Dispatchers.IO를 사용해 백그라운드 쓰레드에서 코루틴을 실행하더라도 await를 호출하면 코루틴이 실행 완료될 때까지 runBlocking 코루틴이 일시 중단돼 대기함
  • 따라서 participantDeferred1.await()가 participantDeferred2 코루틴이 생성되기 전에 호출되면 participantDeferred1 코루틴과 participantDeferred2 코루틴은 순차적으로 처리됨
    • 서로 간에 독립적인 작업을 동시에 처리할 수 있음에도 불구하고 순차적으로 처리하게 되면 매우 비효율적
    • 따라서 participantDeferred1 코루틴의 await를 호출하는 위치를 participantDeferred2 코루틴이 실행된 이후로 만들어 동시에 처리하도록 처리

 

2. awaitAll을 사용한 결괏값 수신하기

  • await 함수를 N번 호출해야하는 경우 await 함수를 N 줄에 걸쳐 호출해야 하기 때문에 가독성이 안 좋음
  • 위 문제를 해결하기 위해 코루틴 라이브러리는 복수의 Deferred 객체로부터 결괏값을 수신하기 위한 awaitAll 함수를 제공


 

3. 컬렉션에 대해 awaitAll 사용하기

  • Collection<Deferred<T>>에 대해 awaitAll 함수를 호출하면 컬렉션에 속한 Deferred들이 모두 완료돼 결괏값을 반환할 때까지 대기

 

 

withContext

 

1. withContext로 async-await 대체하기

  • 코루틴 라이브러리에서 제공되는 withContext 함수를 사용하면 async-await 작업 대체 가능
    • withContext 함수가 호출되면 함수의 인자로 설정된 CoroutineContext 객체를 사용해 block 람다식을 실행하고 완료되면 해당 결과를 반환
    • withContext 함수를 호출한 코루틴은 인자로 받은 CoroutineContext 객체를 사용해 block 람다식을 실행하며, block 람다식을 모두 실행하면 다시 기존의 CoroutineContext 객체를 사용해 코루틴이 재개됨
    • async-await 쌍을 연속적으로 실행했을 때의 동작과 매우 유사함


 

2. withContext의 동작 방식

  •  withContext 함수는 겉보기에는 async와 await를 연속적으로 호출하는 것과 비슷하게 동작하지만 내부적으로 보면 다르게 동작함
    • async-await 쌍은 새로운 코루틴을 생성해 작업을 처리
    • withContext 함수는 실행 중이던 코루틴을 그대로 유지한 채로 코루틴의 실행 환경만 변경해 작업을 처리

 

 

부연 설명

  • runBlocking 함수와 block 람다식을 실행하는 쓰레드와 withContext 함수의 block 람다식을 실행하는 쓰레드는 main과 DefaultDispatcher-worker-1으로 다르지만 코루틴은 coroutine$1으로 같은 것을 확인 가능
  • withContext 함수는 새로운 코루틴을 생성하는 대신 기존의 코루틴에서 CoroutineContext 객체만 바꿔서 실행
  • withContext 함수가 호출되면 실행 중인 코루틴의 실행 환경이 withContext 함수의 context 인자 값으로 변경돼 실행되며 이를 컨텍스트 스위칭이라고 칭함
    • 만약 context 인자로 CoroutineDispatcher 객체가 넘어온다면 코루틴은 해당 CoroutineDispatcher 객체를 사용해 다시 실행됨

 

 

부연 설명

  • withContext를 호출하면 코루틴이 유지된 채로 코루틴을 실행하는 실행 쓰레드만 변경되기 때문에 동기적으로 실행됨
  • async-await 쌍을 사용하면 새로운 코루틴을 만들지만 await 함수를 통해 순차 처리가 돼 동기적으로 실행됨

 

3. withContext 사용 시 주의점

  • 복수의 독립적인 작업이 병렬로 실행돼야 하는 상황에 withContext를 사용할 경우 성능 문제 야기 가능

 

 

부연 설명

  • runBlocking을 통해 실행된 코루틴은 처음에는 메인 쓰레드에서 실행되는데 withContext(Dispatchers.IO)를 사용하면 코루틴을 유지한 채로 실행 쓰레드만 바뀜
  • 따라서 1초 간 대기 후 `Hello`를 반환받고, 다시 1초간 대기 후 `World`를 반환받음
  • 각 withContext 블록의 코드를 실행하는 데는 1초 밖에 안 걸리지만 순차적으로 처리돼 2초의 시간이 걸리게 됨
    • withContext 함수가 새로운 코루틴을 생성하지 않기 때문에 생기는 문제
    • 이 문제를 해결하기 위해서는 withContext를 제거하고, 코루틴을 생성하는 async-await 쌍으로 대체하면서 Deferred 객체에 대한 await 함수 호출을 만든 코루틴이 실행된 뒤에 해야 함

 

 

4. 정리

  • withContext 함수를 사용하면 코드가 깔끔해 보이는 효과를 내지만 잘 못 사용하게 될 경우 코루틴을 동기적으로 실행하도록 만들어 코드 실행 시간이 배 이상으로 증가할 수 있음
  • withContext 함수가 새로운 코루틴을 생성하지 않는다는 것을 명심해야 함

 

참고

코틀린 코루틴의 정석 (조세영 저)

반응형