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

suspend 함수란 무엇인가요?

by Kim Juhwan 2022. 8. 29.

🚀 글 읽는 순서 🚀

코루틴은 왜 빠른 걸까요?
-> suspend 함수란 무엇인가요? (현재)
1. 요약
2. await
   2-1. 개념
   2-2. 코루틴 내부에서 실행되는 await

   2-3. suspend 내부에서 실행되는 await
3. Suspend
   3-1. 개념
   3-2. 내부 코드

4. 정리

 

 

 


 

 

1. 요약

🧑‍💻: suspend 함수란 무엇인가요?

 

👨🏻‍🦱: suspend 함수는 일시 중단 가능한 함수이며
코루틴 내부 혹은 suspend 함수 내부에서 사용해야 합니다.
suspend의 내부 코드는 콜백 함수 형태를 띄고 있습니다.

 

2. await

2-1. 개념

 

이전 글 "코루틴은 왜 빠른 걸까요?"에서 코루틴에 대해 알아보았다.

코루틴은 스레드 안에서 더 잘게 나눠지는 작업 단위라고 표현했었다.

그렇기 때문에 작업 1이 작업 2의 결과를 기다리는 동안 작업 3을 실행할 수 있다.

 

 

그렇다면 작업 1이 진행되다가 잠시 일시 중단되는 저 지점!

코루틴을 일시 중단해야 하는 저 동작을 코드로 어떻게 구현할 수 있을까?

 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        exampleSuspend()
    }

    fun exampleSuspend() {
        val job = CoroutineScope(Dispatchers.IO).async {
            delay(1000) // 대충 시간이 오래 걸리는 작업
        }

        job.await() // 오류 발생!
    }

CoroutineSCope, Dispatchers, async 이런 거는 오늘 포스팅의 주제에서 벗어나므로 자세한 설명은 생략!

간단하게 설명하자면 하나의 코루틴을 만드는 작업이라고 보면 된다.

그리고 그것을 job에 담은 거임

 

중요한 것은 await()이다.

바로 저 await을 통해서 코루틴을 일시 중지시킬 수 있다.

그런데 위 코드를 작성하게 되면 await 부분에 오류가 뜨게 된다.

 

 

오류 내용을 한 번 해석해보자.

Suspend function 'await' should be called only from a coroutine or another suspend function.

(= Suspend 함수 await은 코루틴 혹은 다른 suspend 함수에 의해 실행되어야 합니다)

 

우리가 아직 suspend에 대해 모르므로

일단 await을 코루틴에 의해 실행되게 바꿔보자.

 

2-2. 코루틴 내부에서 실행되는 await

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        exampleSuspend()
    }

    fun exampleSuspend() {
        val job = CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }

        CoroutineScope(Dispatchers.Main).launch { // 코루틴으로 감쌌더니
            job.await() // 오류가 사라졌다!
        }
    }

코루틴 안에서 await을 실행시키니 오류가 사라지는 것을 확인할 수 있다.

우리는 이를 통해

"아~! 코루틴을 일시 중단하는 await 함수는 코루틴 안에서 실행되어야 하는구나!"를 알 수 있다.

 

만약 코루틴으로 감싸지 않으면 기본적으로 메인 스레드에 의해서 돌아가므로

await 함수가 스레드 안에서 실행된다고 보면 된다. -> 이러면 안 된다는 것!

 

2-3. suspend 함수 내부에서 실행되는 await

    /* case 1 */
    fun exampleSuspend() {
        val job = CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }

        job.await() // 오류 발생 ㅠㅠ
    }
    
    
    /* case 2 */
    // suspend를 달아주니
    suspend fun exampleSuspend() {
        val job = CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }

        job.await() // 오류가 발생하지 않는군!
    }

이번엔 await이 다른 suspend 함수에 의해 실행되어야 한다는 말을 확인해보자.

case 2를 보면 await을 코루틴으로 감싸지 않았는데도 오류가 뜨지 않는다.

그 이유는 바로 함수 앞에 suspend 키워드를 붙였기 때문이다.

 

오 뭐야 뭐야 그러면 await을 굳이 코루틴안에 안 넣어도 되는 건가!? 🤔

 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        exampleSuspend() // 오류 발생
    }

    suspend fun exampleSuspend() {
        val job = CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }

        job.await() // suspend 덕분에 여기는 오류가 뜨지 않는다.
    }

아쉽지만 땡!

이번엔 suspend 함수로 만들어버린 exampleSuspend를 호출하는 곳에서 오류가 발생한다.

 

오류 내용은

Suspend function 'exampleSuspend' should be called only from a coroutine or another suspend function.

(= Suspend 함수 exampleSuspend은 코루틴 혹은 다른 suspend 함수에 의해 실행되어야 합니다)

즉, await을 그냥 사용하려고 했을 때와 똑같은 오류가 뜬다.

 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        exampleSuspend2() // 오류 발생
    }
    
    suspend fun exampleSuspend2() {
    	exampleSuspend()
    }

    suspend fun exampleSuspend() {
        val job = CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }

        job.await() // suspend 덕분에 여기는 오류가 뜨지 않는다.
    }

이를 해결하려고 exampleSuspend를 또 다른 suspend 함수 안에 넣어봤자

결국에는 또 그 함수 exampleSuspend2를 호출하면 똑같은 오류가 발생한다.

 

Suspend -> Suspend -> Suspend -> .... -> Coroutine 안에서 호출

결국에는 Suspend 함수를 Suspend 함수 안에서 호출할 수 있지만 최초로 호출하는 곳은 코루틴 내부여야 한다는 것이다.

 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // suspend 함수를 호출하기 위해 코루틴 내부에서 호출
        CoroutineScope(Dispatchers.Main).launch {
            exampleSuspend()
        }
    }

    // await을 사용하기 위해 suspend 함수로 만듦
    suspend fun exampleSuspend() {
        val job = CoroutineScope(Dispatchers.IO).async {
            delay(1000)
        }

        job.await()
    }

바로 이렇게 말이다. (정상 작동)

 

3. Suspend

3-1. 개념

[목차 2-3]에서는 suspend를 어떻게 사용하는지에 대해 알아봤다.

그렇다면 suspend 함수를 호출했을 때 어떤 일들이 벌어지는 지를 알아보자.

 

먼저, Thread 1이 노란색 블록으로 표시한 코루틴을 만들었다고 하자.

 

 

Thread 1은 작업을 수행하다가 일시 중단 함수(Suspend 함수)를 만난다.

그러면 "아~ exampleSuspend 함수 안에 일시 중단이 가능한 작업이 있구나! 나는 그럼 다른 코루틴 수행하러 가야지~"

하고 다른 작업을 하러 가버린다.

 

Thread 2는 그동안 열심히 작업을 해서 결과를 리턴해준다.

 

 

노란 블록 코루틴에서 결과를 받아 무언가 처리하는 로직이 있다고 치자.

그러면 이걸 아까 처음에 Thread 1이 무조건 받아서 처리를 하는 게 아니라

또 탱자탱자 놀고 있는 Thread한테 시켜서 하던 일을 재개한다.

(어떤 Thread를 선택할지는 Dispatcher라는 놈이 처리해준다)

 

이것이 suspend 함수를 호출했을 때 벌어지는 전체적인 프로세스이다.

 

3-2. 내부 코드

suspend 함수는 디컴파일 해보면 내부적으로 콜백 함수 형태를 띠고 있다.

이에 대해서 자세히 정리해둔 포스팅이 있으니 여기를 참고하면 좋을 듯하다.

 

4. 헷갈리는 개념 정리

나만 헷갈린 거일 수 있음 주의

 

처음에 공부할 때 suspend 함수가 일시 중단 가능한 함수라는데

뭐가 뭐를 일시 중단한다는 건지 주어가 없어서 혼란스러웠다.

 

결론부터 말하자면, 위 그림을 기준으로 주황 블록을 호출한 놈(노란 블록)을 일시 중단시킨다.

그러니까 suspend 함수를 호출한 쪽의 코루틴을 일시 중단시킨다는 뜻이다.

노란색 코루틴을 실행시키던 스레드는 할일이 없어지니 다른 코루틴을 실행하러 가는 거고...

뭐 이후에는 [목차 3-1]에서 설명한 내용과 동일하다.

 

이렇게 해서 suspend 함수 공부하기 끝~!

 

 


💡 느낀 점

  • await 내부를 까보니 결국 이것도 suspend 함수였다. suspend 함수이기 때문에 await을 그냥 사용하면 오류가 떴던 것...!
  • 여태까지는 나는 단순히 비동기 처리를 위해서 코루틴을 사용한다 정도로 이해하고 있지 않았나 싶다. 이번 기회에 동작 원리를 알게 됐는데 꽤 흥미로운 듯
  • 코루틴을 공부하면서 정리한 글이라 잘못된 부분이 있을 수도 있습니다. 지적 부탁드려요... ( _ _)

📘 참고한 자료


 

 

반응형

댓글