Coroutine(suspending Functions) - Chapter4

HEETAE HEO·2022년 7월 22일
0
post-thumbnail

앞에 글들에서 저희는 Coroutine Basics과 Coroutine Builder들을 알아봤습니다.
이번 글에서는 Sequentail Execution Concurrent Execution, Lazy Coroutine Excecution에 대해서 알아보도록 하겠습니다.

코드를 예시로 설명하겠습니다.

Sequentail Execution

suspend fun getMessageOne() : String {
	delay(1000L) // pretend to do some work
    return "Hello"
}

suspend fun getMessageTwo() : String {
	delay(1000L) // pretend to do some work 
    return "World"
}

2개의 suspend 함수가 있습니다. 하나는 Hello를 다른 하나는 World라는 단어를 리턴하는 String 함수이며 delay(1000) 이 존재합니다.

fun main() = runBlocking {
	
    println("Main program starts : ${Thread.currentThread().name}")
    
   	val msgOne = getMessageOne()
    val msgTwo = getMessageTwo()
    println("The entire message is : ${msgOne + msgTwo}")
    
    
    println("Main program ends : ${Thread.currentThread().name}") 


}

다음과 같이 runBlocking 내부에서 호출을 해 Print해주는 코드를 작성합니다.
결과는 다음과 같이 출력이 됩니다.

Main program starts : main 
The entire message is : Hello World
Main program ends : main

과연 이 두 함수는 어떻게 동작하였을까요? 함수들이 순차적으로 동작하였을까요? 아니면 동시에 동작하였을까요? 다음과 동작시간을 통해 확인해보겠습니다.

fun main() = runBlocking {
	
    println("Main program starts : ${Thread.currentThread().name}")
    
    val time = measureTimeMillis {
       	val msgOne = getMessageOne()
    	val msgTwo = getMessageTwo()
        println("The entire message is : ${msgOne + msgTwo}")
    }
    println("Completed in $time ms")

    println("Main program ends : ${Thread.currentThread().name}") 


}

이렇게 수정을 해주고 동작을 시켜 걸리는 시간을 확인해보겠습니다.

Main program starts : main 
The entire message is : Hello World
Completed in 2023 ms
Main program ends : main

와 같이 결과가 나오게됩니다. 결과를 보면 2초 이상이 걸린 것을 확인할 수 있습니다. 이 뜻은 getMessageOne()에서 1초를 대기한 후 getMessageTwo()에서 1초가 더 걸렸다는 것을 알 수 있습니다.
이를 통해 suspend 함수가 동작할 때는 순차적으로 동작한다는 것입니다.

코루틴 내부의 코드들은 기본적으로 순차적으로 동작한다는 것을 알 수 있었습니다.

Concurrent Execution

Concurrent의 의미는 Parallel입니다. 즉 동시에 라는 뜻으로 알고 계시면 됩니다.

위의 코드에서 두 함수를 참조할 때 runBlocking내부의 코루틴이 아닌 async를 통해 자식 코루틴을 하나 더 생성하여 불러와보겠습니다.

fun main() = runBlocking {
	
    println("Main program starts : ${Thread.currentThread().name}")
    
    val time = measureTimeMillis {
       	val msgOne : Deferred<String> = async { getMessageOne() }
    	val msgTwo : Deferred<String> = async { getMessageTwo() }
        println("The entire message is : ${msgOne.await() + msgTwo.await() }")
    }
    println("Completed in $time ms")

    println("Main program ends : ${Thread.currentThread().name}") 


}

async를 사용할 수 있게 Deferred 타입으로 선언해주는데 2개의 함수가 String 값을 return 하는 함수이기에 String을 넣어줍니다. 그리고 하나의 함수가 먼저 끝나도 다른 코루틴을 기다리도록 await또한 추가해줍니다. 해당 코드를 동작시킨다면 다음과 같이 동작합니다.

Main program starts : main 
The entire message is : Hello World
Completed in 1050 ms
Main program ends : main

함수를 호출하고 출력하는데 걸리는 시간이 1초대로 줄어들었습니다. 이는 두 함수가 순차적으로 동작한 것이 아닌 동시에 작업을 했다는 것을 알 수 있습니다. runBlocking 코루틴 내에서 자식 코루틴을 추가로 생성하여 두개의 함수를 동시에 동작하도록 해준 것입니다. 그렇기에 동작시간이 동작이 1초로 줄어든 것입니다.

Lazy Execution

fun main() = runBlocking {
	
    println("Main program starts : ${Thread.currentThread().name}")
    
    val msgOne : Deferred<String> = async { getMessageOne() }
    val msgTwo : Deferred<String> = async { getMessageTwo() }
    
   // println("The entire message is : ${msgOne.await() + msgTwo.await() }")
    
    println("Main program ends : ${Thread.currentThread().name}") 

		suspend fun getMessageOne() : String {
		delay(1000L) // pretend to do some work
        println("After working in getMessageOne()")
   	 	return "Hello"
	}

	suspend fun getMessageTwo() : String {
		delay(1000L) // pretend to do some work 
         println("After working in getMessageTwo()")
    	return "World"
	}
}

위의 코드를 동작시키면 다음과 결과를 출력하게 됩니다.

Main program starts : main
Main program ends : main
Atfer working in getMessageOne()
After working in getMessageTwo()
fun main() = runBlocking {
	
    println("Main program starts : ${Thread.currentThread().name}")
    
    val msgOne : Deferred<String> = async(start = CoroutineStart.Lazy) { getMessageOne() }
    val msgTwo : Deferred<String> = async(start = CoroutineStart.Lazy) { getMessageTwo() }
    
   // println("The entire message is : ${msgOne.await() + msgTwo.await() }")
    
    println("Main program ends : ${Thread.currentThread().name}") 

		suspend fun getMessageOne() : String {
		delay(1000L) // pretend to do some work
        println("After working in getMessageOne()")
   	 	return "Hello"
	}

	suspend fun getMessageTwo() : String {
		delay(1000L) // pretend to do some work 
         println("After working in getMessageTwo()")
    	return "World"
	}
}

async에 start를 Lazy로 해주었습니다. 다음과 같이 사용하게 된다면 결과는 다음과 같습니다.

Main program starts : main
Main program ends : main

그 이유는 다음과 같습니다. Lazy로 선언이 되었기에 해당 변수를 사용하는 시점에 해당 함수를 가지고 오겠다고 선언되어있는 상태에서 해당 변수를 사용하지 않았기에 자식 코루틴이 동작을 하지않은 것입니다.

다음은 println의 주석을 제거하고 동작시켜보면 결과는 다음과 같습니다.

Main program starts : main
Atfer working in getMessageOne()
After working in getMessageTwo()
The entire message is : Hello World
Main program ends : main

이번에는 변수를 사용하는 시점에 함수를 불러왔기에 다음과 같이 결과를 출력하게 됩니다. 해당 코드의 동작에 대해 설명드리자면
println("The entire message is : ${msgOne.await() + msgTwo.await() }") 해당 코드에서 코드가 msgOne.await()에 도착을 하게되면 이때 함수를 호출합니다. 그리고 해당 동작을 마친 이후에 msgTwo에 가게되고 그때 getMessageTwo()를 호출하게 됩니다.

이렇게 Coroutine의 동작 방식에 대해서 알아보는 시간을 가져봤습니다. 다음 글에서는 CoroutineScope, CoroutineContext, Dispatchers에 대해서 알아보도록 하겠습니다. 읽어주셔서 감사합니다.

profile
Android 개발 잘하고 싶어요!!!

0개의 댓글