관리 메뉴

나만을 위한 블로그

[Android] withContext란? 본문

Android

[Android] withContext란?

참깨빵위에참깨빵 2023. 4. 8. 23:49
728x90
반응형

코루틴 예제들을 찾아보다 보면 가끔 withContext라는 걸 볼 수 있다. 코틀린 공식문서에서 설명하는 withContext는 아래와 같다.

 

https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html

 

withContext

Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result. The resulting context for the block is derived by merging the current coroutineContext with the specified context using coroutineConte

kotlinlang.org

지정된 코루틴 컨텍스트를 써서 일시 중단 블록을 호출하고 완료될 때까지 일시 중단한 다음 결과를 반환한다. 블록에 대한 결과 컨텍스트는 coroutineContext + context를 써서 현재 coroutineContext를 지정된 컨텍스트와 병합해 파생된다. 이 정지 함수는 취소할 수 있다. 결과 컨텍스트의 취소를 즉시 확인하고 활성화하지 않은 경우 CancellationException을 발생시킨다. 컨텍스트 인수가 현재 것과 다른 CoroutineDispatcher를 제공하는 withContext에 대한 호출은 필요에 따라 추가 디스패치를 수행한다. 블록은 즉시 실행될 수 없으며 전달된 CoroutineDispatcher에서 실행을 위해 디스패치되어야 한다. 그런 다음 블록이 완료되면 실행은 원래 디스패처로 다시 이동해야 한다. withContext 호출의 결과는 즉시 취소 보장과 함께 취소 가능한 방식으로 원래 컨텍스트로 발송된다

 

안드로이드 디벨로퍼에서도 withContext에 대해 말하는 내용이 있어 가져왔다.

 

https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=ko#perf 

 

Kotlin 코루틴으로 앱 성능 향상  |  Android Developers

Kotlin 코루틴으로 앱 성능 향상 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Kotlin 코루틴을 사용하면 네트워크 호출이나 디스크 작업과 같은 장기 실행 작

developer.android.com

withContext()는 상응하는 콜백 기반 구현에 의해 오버헤드를 추가하지 않는다. 또한 일부 상황에서 상응하는 콜백 기반 구현을 능가하도록 withContext() 호출을 최적화할 수 있다. 함수가 네트워크를 10번 호출하는 경우 외부 withContext()를 써서 쓰레드를 한 번만 전환하도록 코틀린에 지시할 수 있다. 그러면 네트워크 라이브러리에서 withContext()를 여러 번 쓰더라도 동일한 디스패처에 유지되고 쓰레드가 전환되지 않는다. 또한 코틀린은 가능한 한 쓰레드 전환을 방지하도록 Dispatchers.Default와 Dispatchers.IO 간의 전환을 최적화한다

 

https://www.geeksforgeeks.org/withcontext-in-kotlin-coroutines/

 

withContext in Kotlin Coroutines - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

async, launch는 코루틴을 시작하는 2가지 방법으로 알려져 있다. async는 결과를 받기 위해 쓰는 것으로 알려져 있어 &는 병렬 실행이 필요할 때만 써야 하는 반면, launch는 결과를 돌려받고 싶지 않을 때 사용하며 업데이트 같은 작업에 사용된다. 비동기가 지금까지 코루틴을 시작하고 결과를 다시 가져오는 유일한 방법이라는 걸 알고 있지만 비동기의 문제는 병렬 네트워크 호출을 원하지 않을 때 발생한다. async를 쓸 때 await()를 써야 메인 쓰레드가 차단되는 것으로 알려져 있지만 여기선 메인 쓰레드 차단 문제를 제거하는 withContext 개념을 소개한다. withContext()는 await()를 작성할 필요가 없는 비동기를 작성하는 또 다른 방법일 뿐이다. withContext를 쓰면 작업을 직렬로 실행한다. 따라서 백그라운드에 단일 작업이 있고 해당 작업의 결과를 반환하려는 경우 withContext를 사용해야 한다

 

https://androidwave.com/coroutines-withcontext-example/

 

Coroutines withContext example

In this coroutines withContext example and we'll see how we can change context easily in Coroutines. We'll learn different ways to switch coroutine

androidwave.com

withContext를 쓰면 컨텍스트를 쉽게 바꿀 수 있다. 이것은 디스패처를 쉽게 전환한다는 걸 의미한다. 안드로이드 앱에서 수행해야 하는 무거운 처리가 있을 때 Default Dispatchers에서 수행하고 완료되면 UI에 결과를 표시하기 위해 Main Dispatchers로 바꾸려고 한다. 따라서 컨텍스트 간에 쉽게 전환할 수 있는 방법이 필요한데 이걸 수행하는 가장 좋은 방법은 withContext()다. 가볍기 때문이다
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

fun main() {
    runBlocking {
        launch(Dispatchers.Default) {
            // default context
            println("First context: $coroutineContext")
            withContext(Dispatchers.IO) {
                // IO context
                println("Second context: $coroutineContext")
            }
            // back to default context
            println("Third context: $coroutineContext")
        }
    }
}

// First context: [StandaloneCoroutine{Active}@39f18e56, Dispatchers.Default]
// Second context: [DispatchedCoroutine{Active}@6c6fe848, Dispatchers.IO]
// Third context: [StandaloneCoroutine{Active}@39f18e56, Dispatchers.Default]
이 코드에선 먼저 Default 디스패처에서 코루틴을 시작한다. 그리고 coroutineContext를 출력한다. 그 후 디스패처를 IO 디스패처로 바꾼다. 따라서 withContext(Dispatchers.IO)를 써서 2번째 컨텍스트를 출력한다. 완료되면 1번째 컨텍스트로 돌아간다. 따라서 기본 컨텍스트에서 코루틴을 시작한 다음 IO로 전환하고 다시 원래 상태로 돌아간다...(중략)...따라서 기본적으로 기능 유형, 수행해야 하는 처리 유형에 따라 컨텍스트를 전환하는 매우 가벼운 방법이다

 

withContext에 대해 정리하면 아래와 같다.

 

  • 코루틴 라이브러리에서 제공하는 함수. 코루틴의 실행 컨텍스트(쓰레드 or 디스패처)를 바꿀 때 사용한다
  • 현재 코루틴이 실행 중인 컨텍스트에서 다른 컨텍스트로 잠깐 전환해 중괄호 블록 안의 코드를 실행할 수 있다
  • 시간이 오래 걸리는 작업을 메인 쓰레드에서 분리해 UI를 차단하지 않게 하거나, 다른 쓰레드 or 디스패처에서 실행해야 하는 코드가 있을 경우 사용한다

 

메인 쓰레드와 다른 실행 공간을 만들고 거기서 중괄호 블록 안의 코드들을 실행한다고 보면 된다. 다른 예제 코드를 보자.

 

import kotlinx.coroutines.*
import kotlin.random.Random

suspend fun main() {
    println("메인 코루틴 시작")

    val result = withContext(Dispatchers.IO) {
        executeLongTimeTask()
    }

    println("result : $result")
    println("메인 코루틴 종료")
}

suspend fun executeLongTimeTask(): Int {
    delay(1000L)
    return Random.nextInt(100)
}

 

메인 함수를 실행하면 어떤 결과가 표시될까? 일단 메인 코루틴 시작 문자열을 먼저 출력할 것이다.

그 다음에는 withContext {} 가 호출되는데, withContext {} 안의 코드 실행과 resut 변수 출력, 메인 코루틴 종료 문자열 출력 중 어느 게 가장 먼저 실행될까?

withContext {} 가 호출되면 메인 쓰레드는 잠시 멈추고, IO 디스패처를 사용하는 별도의 작업 공간에서 executeLongTimeTask()를 실행한다. 이 함수의 영향으로 1초 뒤 0~100까지의 숫자 중 랜덤한 숫자를 하나 뽑아서 result 변수에 담은 뒤 withContext는 종료된다. 이후 result 변수가 출력되고 메인 코루틴 종료까지 표시된다.

즉 비동기 코드 안에서 withContext {}를 사용하면 동기식으로 코드가 작동하게 할 수 있다. 그러나 모든 것은 과하게 사용하면 부작용이 있듯 withContext 또한 필요 이상으로 사용하면 부작용이 발생할 수 있다.

 

  • 성능 저하 : 코루틴의 실행 컨텍스트를 바꾸게 되면 오버헤드가 발생한다. 이 때문에 코루틴의 전체 실행 시간이 오래 걸리게 되고, 결과적으로 앱 성능이 저하될 수 있다
  • 코드 복잡성 증가, 가독성 감소 : 위의 특징을 가진 withContext를 많이 쓰면 코드 복잡성이 증가하고 디버깅이 어려워지면서 유지보수에 어려움이 발생할 수 있다

 

또한 IO 작업은 Dispatchers.IO에서 돌아가게 하는 등 준비된 디스패처 중에서 해야 하는 작업에 따라 적절한 디스패처를 선택해야 한다.

반응형
Comments