본문 바로가기
오늘은 뭘 배울까?/Android

withContext는 무엇이며 async와 무슨 차이가 있을까?

by Kim Juhwan 2022. 9. 7.

🚀 글 읽는 순서 🚀

코루틴은 왜 빠른 걸까요?
suspend 함수란 무엇인가요?
Coroutine Dispatcher, 넌 대체 뭐야?
-> withContext는 무엇이며 async와 무슨 차이가 있을까? (현재)
1. 요약
2. Dispatcher와 CoroutineContext
3. withContext
   3-1. 개념
   3-2. async와의 차이

 

 

 


 

 

1. 요약

🧑‍💻: withContext는 왜 사용할까요?? async와는 무슨 차이가 있을까요?

 

👨🏻‍🦱: 코루틴의 context를 변경하고 싶을 때나
비동기 코드를 순차적 실행할 때 사용합니다.

asnyc보다 withContext가 더 빠르나 무의미한 수준이고
async는 병렬 처리가 가능하고 withContext는 순차 처리만 가능합니다.
또, async 내에서 발생한 예외는 try~catch로 잡을 수 없으며
withContext는 예외 처리가 가능합니다.

 

2. Dispatcher와 CoroutineContext

// 무거운 연산에 최적화된 작업을 할때
CoroutineScope(Dispatchers.Default).launch {
	
}

// UI 작업을 할때
CoroutineScope(Dispatchers.Main).launch {
	
}

// 네트워크, DB 작업 등에 최적화된 작업을 할때
CoroutineScope(Dispatchers.IO).launch {
	
}

이전 게시물에서 Dispatcher에 대해 설명했지만, witchContext와 밀접한 연관이 있어서 가볍게 언급하고 넘어가려고 한다.

Dispatcher는 코루틴을 배분하는 놈이며

어떤 작업을 하고 싶느냐에 따라 그에 맞는 Dispatcher를 사용해주면 된다고 했다.

 

CoroutineScope 내부 코드

 

우리는 CoroutinScope를 사용할 때 CoroutineContext type을 넘겨주는데

CoroutineContext는 코루틴이 실행되는 환경 정보를 가진다.

IO 작업에 맞는 실행 환경을 가질 건지, CPU 작업에 맞는 실행 환경을 가질 건지 등의 정보가 여기에 포함된다.

 

이 이야기를 하는 이유는 withContext의 context가 바로 저 CoroutineContext를 의미하기 때문이다.

사전 정보는 알았으니 이제 본격적으로 withContext에 대해 알아보자.

 

3. withContext

3-1. 개념

withContext 사용 예시

 

withContext는 나 여기서는 이 실행 환경으로 코루틴 돌릴래요~ 라고 하는 것과 같다.

(아마 네이밍이 그래서 with context 인 듯)

위 코드를 보면 주황색으로 칠해진 부분은 Dispatcher.IO 환경에서 코루틴을 실행하고

초록색 부분에서는 Dispatcher.Main 환경에서 실행이 된다.

즉, 코루틴 실행 도중 실행 환경을 바꾸고 싶을 때 사용한다.

 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
 
        CoroutineScope(Dispatchers.Main).launch {
            exampleSuspend()
        }
    }
 
    suspend fun exampleSuspend() {
        val job = CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }
 
        job.await()
    }

즉, async와 await을 사용하면 이렇게 작성해야 할 코드를

 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
 
        CoroutineScope(Dispatchers.Main).launch {
            exampleSuspend()
        }
    }
 
    suspend fun exampleSuspend() {
        withContext(Dispatchers.IO) {
            delay(1000)
        }
    }

이렇게 작성할 수 있다는 것이다.

(만약 await에 대해 모른다면 여기에서 확인할 수 있다)

 

    suspend fun exampleSuspend() {
        // 1
        withContext(Dispatchers.IO) {
            delay(1000) // 2
        }
        // 3
    }

withContext 코드 내부를 까보면 결국엔 이것도 suspend 함수이기 때문에

withContext {} 내의 코드가 다 끝날 때까지 정지가 된다.

위에 주석을 달아둔 순서대로 코드가 실행이 된다는 것이다.

즉, withContext를 사용하면 순차적으로 코드가 실행되는 것처럼 구현을 할 수 있다.

 

3-2. async 와의 차이

[목차 3-1] 설명대로라면 withContext는 async-wait과 거의 흡사하다.

음... 코드가 조금 간결해지는구나 정도?

그러면 사용하기 쉬운 withContext를 사용하는 게 장땡일까? 그렇지 않다.

어떤 차이가 있는지 자세히 알아보자.

 

  • withContext가 async보다 2배 성능이 좋다.

stackoverflow의 한 유저가 두 함수를 비교한 결과가 있다. (링크)

하지만 말이 2배지 사실상 속도가 nanoseconds급으로 빠르기 때문에 크게 의미가 없다고 한다.

비유를 하자면 여러분들이 하는 게임에서 뽑기 2배 이벤트를 한다고 하자.

원래 확률이 50%면 -> 100%로 늘어나니까 대박 이벤트지만

원래 확률이 0.00001%면 -> 0.00002% 니까 사실상 아무 의미가 없다.

마찬가지로 withContext와 async의 속도 차이는 사실상 아무 의미가 없다.

둘 중 뭘 사용할까에 대한 기준으로 속도는 의미가 없다는 것!

(엄청난 양의 작업을 한다면 의미가 있을지 모르지만 일반적인 경우는 아니므로..)

 

  • 병렬 처리
    suspend fun exampleSuspend() {
        withContext(Dispatchers.IO) {
            delay(1000)
        }

        withContext(Dispatchers.IO) {
            delay(1000)
        }

        withContext(Dispatchers.IO) {
            delay(1000)
        }
    }

withContext는 순차적인 실행은 되지만 병렬 실행이 되지 않으므로

위 코드를 실행하기 위해서는 약 3초의 시간이 소요된다.

 

    suspend fun test() {
        CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }

        CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }

        CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }
    }

하지만 async를 사용하면 위 코드는 약 1초의 시간이 소요된다.

즉, 하나의 작업을 비동기 처리하는 상황에서는 withContext를 사용하는 것이 낫지만

병렬 처리를 해야 하는 상황에서는 async를 사용해야 한다는 것이다.

 

  • 예외 처리
        CoroutineScope(Dispatchers.IO).launch { // 2. 여기까지 에러가 전달된다.
            try {
                async {
                    throw Exception() // 1. 여기서 에러를 발생시키면
                }
            } catch (e: Exception) {
                println("error catch")
            }
        }

async를 사용하면 exception이 발생하는 부분에서 try~catch를 사용해도

코루틴을 실행시킨 CoroutineScope에도 예외가 전달된다.

그래서 위 코드는 실행시키지 못하고 앱이 죽어버린다.

(정확히는 async가 그렇다기보단... launch도 마찬가지다)

 

        CoroutineScope(Dispatchers.IO).launch {
            try {
                withContext(Dispatchers.Default) {
                    throw Exception()
                }
            } catch (e: Exception) {
                println("error catch")
            }
        }

반면 withContext는 예외가 전달되지 않아 Exception을 잡아낸다는 차이가 있다.

 

이렇게 차이를 알아보았으니

앞으로는 상황에 맞게 withContext를 사용하자! 😄

 

 


💡 느낀 점

  • withContext를 순차적 실행을 위해서만 사용했는데... 이런 특징과 차이점이 있는 줄 몰랐다. 👉👈 반성...
  • async를 사용할 때 try~catch가 예외를 잡지 못한다는 것도 이번에 처음 알았다. 끙. 여태까지 예외 처리를 잘못하고 있었네...
  • 왜 바깥쪽 scope에까지 예외를 전파하는 걸까? error handling에 대한 내용도 다음에 알아봐야겠다.

📘 참고한 자료


 

 

반응형

댓글