본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

[coroutine] withContext vs async

by 돼지왕 왕돼지 2022. 2. 1.
반응형

 

-
withContext 와 async 를 언제 어떻게 쓰면 좋을까?

 

-
동일한 구문을 돌리는 경우 withContext 가 async 보다 약 2배의 성능이 좋았다는 테스트 결과가 있다.
https://stackoverflow.com/questions/50230466/kotlin-withcontext-vs-async-await

for(1..1_0000_0000){
    withContext(context){
      // do sth
    }
}

vs

for(1..1_0000_0000){
    async(context){
        // do sth
    }.await()
}

메모리 사용량은 큰 차이가 없다고 한다.

 

-
직접 성능 측정해 보았다.

launch {
    val duration = measureTimeMillis {
        for (i in 1..10_0000) {
            withContext(Dispatchers.Default) {
                Log.e("cklee", "throw exception")
            }
        }
    }
    Log.e("cklee", "duration = $duration ms")
}

// duration = 11653 ms

 

launch {
    val duration = measureTimeMillis {
        for (i in 1..10_0000) {
            async(Dispatchers.Default) {
                Log.e("cklee", "throw exception")
            }.await()
        }
    }
    Log.e("cklee", "duration = $duration ms")
}

// duration = 12599 ms

 

10만번에 대한 로그를 찍는 행위는 12599 - 11653 = 946ms 약 1초 차이가 났다.
10회만 테스트 했을 때 둘 다 6~7ms 가 걸렸다.
간단한 1~2회성 작업에 대해서는 사실 큰 차이가 나지 않는다고 볼 수 있고, 엄청난 양의 작업을 할 때는 parallel 이 필요 없다면 withContext 를 쓰는 정도로 이해해야 맞겠다.

 

-
async 는 parallel job 을 수행할 수 있지만, withContext 는 sequential 만 가능하다.

 

-
main thread 의 async 의 경우 exception 이 발생시 catch 를 해도 coroutine scope 까지 전달이 되어 앱이 죽는다.

launch {
    try {
        async(Dispatchers.Default) {
            Log.e("cklee", "throw exception")
            throw IllegalStateException("This is exception")
        }.await()
    } catch (e: Exception) {
        Log.e("cklee", "exception $e")
    }
}

// throw exception
// exception java.lang.IllegalStateException: This is exception
// <Crash>

 

val defaultExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> Log.e("cklee", "coroutine exception handler $throwable") }
launch(defaultExceptionHandler) {
    try {
        async(Dispatchers.Default) {
            Log.e("cklee", "throw exception")
            throw IllegalStateException("This is exception")
        }.await()
    } catch (e: Exception) {
        Log.e("cklee", "exception $e")
    }
}

// throw exception
// exception java.lang.IllegalStateException: This is exception
// coroutine exception handler java.lang.IllegalStateException: This is exception

defaultExceptionHandler 를 쓰지 않고 앱이 crash 나지 않게 하는 방법 중 하나는 supervisorScope{ } 의 사용이다.

 

-
그러나 withContext 의 경우 해당 구문을 try - catch 로 쉽게 잡을 수 있다.

launch {
    try {
        withContext(Dispatchers.Default) {
            Log.e("cklee", "throw exception")
            throw IllegalStateException("This is exception")
        }
    } catch (e: Exception) {
        Log.e("cklee", "exception $e")
    }
}

// throw exception
// exception java.lang.IllegalStateException: This is exception

 

val defaultExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> Log.e("cklee", "coroutine exception handler $throwable") }
launch(defaultExceptionHandler) {
    try {
        withContext(Dispatchers.Default) {
            Log.e("cklee", "throw exception")
            throw IllegalStateException("This is exception")
        }
    } catch (e: Exception) {
        Log.e("cklee", "exception $e")
    }
}

// throw exception
// exception java.lang.IllegalStateException: This is exception

withContext 의 경우 coroutineScope 으로 exception 이 전파되지 않기 때문에 defaultExceptionHandler 에 걸리지 않는다.

 

 

 

반응형

댓글