새소식

Language/Kotlin

[Kotlin] Coroutine 실행 순서 살펴보기

  • -
코루틴 실행순서를 간단하게 테스트해 보았습니다. 
코루틴의 개념은 다루지 않습니다!
class MainActivity : AppCompatActivity() {
    private val mainActivityViewModel: MainActivityViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mainActivityViewModel.testCoroutine()
    }
}

 

 

[1] 하나의 코루틴스코프 내에 실행 순서

private const val TAG = "MainActivityViewModel_Test"
class MainActivityViewModel : ViewModel(){
    val scopeIO = CoroutineScope(IO)

    fun testCoroutine() {
        scopeIO.launch {
            task1()
            task2()
            task3()
        }
    }

    suspend fun task1() {
        Log.d(TAG, "task1: Start")
        delay(1000)
        Log.d(TAG, "task1: end")
    }

    suspend fun task2() {
        Log.d(TAG, "task2: Start")
        delay(1000)
        Log.d(TAG, "task2: end")
    }

    suspend fun task3() {
        Log.d(TAG, "task3: Start")
        delay(1000)
        Log.d(TAG, "task3: end")
    }
}
결과 
task1: Start
task1: end
task2: Start
task2: end
task3: Start
task3: end

 

 

[2] 하나의 코루틴스코프 내에 실행 순서

private const val TAG = "MainActivityViewModel_Test"
class MainActivityViewModel : ViewModel(){
    val scopeIO = CoroutineScope(IO)

    fun testCoroutine() {
        scopeIO.launch {
            val string = task1()
            Log.d(TAG, "string: $string")
            task2()
            task3()
        }
    }

    suspend fun task1(): String{
        Log.d(TAG, "task1: Start")
        delay(4000)
        return "test!!!"
    }

    suspend fun task2() {
        Log.d(TAG, "task2: Start")
        delay(2000)
        Log.d(TAG, "task2: end")
    }

    suspend fun task3() {
        Log.d(TAG, "task3: Start")
        delay(1000)
        Log.d(TAG, "task3: end")
    }
}
결과
task1: Start
string: test!!!
task2: Start
task2: end
task3: Start
task3: end

같은 코루틴 스코프 내에 실행 순서가 보장되는 것을 확인할 수 있다.

 

 

[3] 다른 코루틴스코프 내에 실행 순서

private const val TAG = "MainActivityViewModel_Test"
class MainActivityViewModel : ViewModel(){
    val scopeIO = CoroutineScope(IO)

    fun testCoroutine() {
        scopeIO.launch {
            scopeIO.launch { task1() }
            task2()
            task3()
        }
    }

    suspend fun task1() {
        Log.d(TAG, "task1: Start")
        delay(2000)
        Log.d(TAG, "task1: end")
    }

    suspend fun task2() {
        Log.d(TAG, "task2: Start")
        delay(4000)
        Log.d(TAG, "task2: end")
    }

    suspend fun task3() {
        Log.d(TAG, "task3: Start")
        delay(1000)
        Log.d(TAG, "task3: end")
    }
}
결과
task2: Start
task1: Start
task1: end
task2: end
task3: Start
task3: end

task1은 독립적인 코루틴스코프에서 실행하기 때문에 작업 순서가 독립적인 것을 확인할 수 있습니다. task2, task3는 서로 순서를 보장하는 것 또한 확인할 수 있습니다.

 

 

[4] 다른 코루틴스코프 내에 실행 순서

위 예시에서 task3도 다른 코루틴 스코프에서 실행되도록 해보겠습니다.

private const val TAG = "MainActivityViewModel_Test"
class MainActivityViewModel : ViewModel(){
    val scopeIO = CoroutineScope(IO)

    fun testCoroutine() {
        scopeIO.launch {
            scopeIO.launch { task1() }
            task2()
            scopeIO.launch { task3() }
        }
    }

    suspend fun task1() {
        Log.d(TAG, "task1: Start")
        delay(2000)
        Log.d(TAG, "task1: end")
    }

    suspend fun task2() {
        Log.d(TAG, "task2: Start")
        delay(4000)
        Log.d(TAG, "task2: end")
    }

    suspend fun task3() {
        Log.d(TAG, "task3: Start")
        delay(1000)
        Log.d(TAG, "task3: end")
    }
}
결과
task2: Start
task1: Start
task1: end
task2: end
task3: Start
task3: end

task3의 실행은 별도의 코루틴스코프에서 실행되지만, task2가 끝나기 전 까지는 실행되지 않는 것을 확인할 수 있습니다.

 

 

[5] 다른 코루틴스코프 내에 실행 순서

private const val TAG = "MainActivityViewModel_Test"
class MainActivityViewModel : ViewModel(){
    val scopeIO = CoroutineScope(IO)

    fun testCoroutine() {
        scopeIO.launch {
            scopeIO.launch { task1() }
            scopeIO.launch { task2() }
            scopeIO.launch { task3() }
        }
    }

    suspend fun task1() {
        Log.d(TAG, "task1: Start")
        delay(2000)
        Log.d(TAG, "task1: end")
    }

    suspend fun task2() {
        Log.d(TAG, "task2: Start")
        delay(4000)
        Log.d(TAG, "task2: end")
    }

    suspend fun task3() {
        Log.d(TAG, "task3: Start")
        delay(1000)
        Log.d(TAG, "task3: end")
    }
}
task3: Start
task2: Start
task1: Start
task3: end
task1: end
task2: end

모두 각각의 코루틴스코프에서 실행한 결과를 보면, 각 task가 순서를 보장하지 않고 실행되는 것을 확인할 수 있습니다.

 

 

[6] 각 task의 순서를 보장하게 하기 - join()

위 예시들은 delay를 직접 적용해서 각 작업이 얼마나 걸리는지 예측을 할 수 있습니다.

그러나 Network 통신, DB 작업을 하는 경우 각 작업의 정확한 실행 시간을 예측할 수 없고, 순서를 보장해야 할 경우가 발생합니다.

예를 들어 task1()은 Room에서 데이터를 가져오는 함수입니다. task2()는 task1()의 데이터로 Room데이터를 가져옵니다. task3()는 task2()의 데이터로 api를 호출합니다. 이 경우 각 task는 실행 순서가 보장되어야 합니다.

join()으로 실행 순서 보장

private const val TAG = "MainActivityViewModel_Test"
class MainActivityViewModel : ViewModel(){
    val scopeIO = CoroutineScope(IO)

    fun testCoroutine() {
        scopeIO.launch {
            scopeIO.launch { task1() }.join()
            scopeIO.launch { task2() }.join()
            scopeIO.launch { task3() }
        }
    }

    suspend fun task1() {
        Log.d(TAG, "task1: Start")
        delay(2000)
        Log.d(TAG, "task1: end")
    }

    suspend fun task2() {
        Log.d(TAG, "task2: Start")
        delay(4000)
        Log.d(TAG, "task2: end")
    }

    suspend fun task3() {
        Log.d(TAG, "task3: Start")
        delay(1000)
        Log.d(TAG, "task3: end")
    }
}
결과
task1: Start
task1: end
task2: Start
task2: end
task3: Start
task3: end

join()을 사용하여 각 task1의 작업이 끝나면 task2가 실행

task2 작업이 끝나면 task3 작업이 실행되도록 했습니다.

 

 

[7] 각 task 순서 보장 - withContext()

withContext는 현재 코루틴을 중단하고 지정된 디스패처에서 코드 블록을 실행한 후, 블록이 완료되면 다시 원래 코루틴 콘텍스트로 돌아옵니다. 즉, withContext 블록이 완료되기 전까지 같은 코루틴에 있는 다음 코드를 실행하지 않습니다.

private const val TAG = "MainActivityViewModel_Test"
class MainActivityViewModel : ViewModel(){
    val scopeIO = CoroutineScope(IO)

    fun testCoroutine() {
        scopeIO.launch {
            withContext(IO) {
                task1()
            }
            withContext(IO) {
                task2()
            }
            withContext(IO) {
                task3()
            }
        }
    }

    suspend fun task1() {
        Log.d(TAG, "task1: Start")
        delay(2000)
        Log.d(TAG, "task1: end")
    }

    suspend fun task2() {
        Log.d(TAG, "task2: Start")
        delay(4000)
        Log.d(TAG, "task2: end")
    }

    suspend fun task3() {
        Log.d(TAG, "task3: Start")
        delay(1000)
        Log.d(TAG, "task3: end")
    }
}
task1: Start
task1: end
task2: Start
task2: end
task3: Start
task3: end
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.