본문 바로가기

Android/Troubleshooting

코루틴 작업 분리

이전의 DB 작업의 연장선이다.

 

나는 api 통신 후, 해당 결과값을 바탕으로 UI를 갱신하는 것과 DB 갱신을 병렬적으로 진행시키고 싶었다. 그게 더 작업 속도를 빠르게 할 수 있을거라고 생각하기 때문이다.

 

뭐.. 대충 이런 흐름을 원했다.

그림이 좀 이상하긴 한데ㅋㅋ DB갱신은 별도로 분리하지 않고, ApiCall() 코드의 호출 성공 부분에 함께 작성해 뒀다. (가독성이 좋아서)

아무튼, 성공시 DB를 갱신하는 것과, 서버로 부터 수신한 데이터를 반환하는 코드가 병렬로 수행되도록 하는 것이 목표라는 것이다.

 

val data = withContext(Dispatcher.IO){ApiCall()}

ApiCall()이 withContext(Dispatcher.IO), I/O 스레드에서 진행이 되기 때문에 사실 아래 처럼 하면 스레드가 하나 더 생성되어 별도로 작업이 수행 될 줄 알았다.

 

suspend fun ApiCall():String{

	runCatching{
    	...
    }.onSuccess{
    	...
        Timber.e("1111")
        // 다른 코드
        withContext(Dispatcher.IO){
            // DB 갱신 코드
            Timber.e("2222")
        }
        Timber.e("3333")
        ...
    }.onFailure{
    	...
    }
    Timber.e("4444")
    
    return(...)
    
}

 

이걸 작성하면서 나는 로그 표시 순서가 "(1111) -> (3333) -> ( 4444 혹은 2222)" 이럴 것이라고 생각했다.

 

물론 아니었다.

그냥 다 순차적으로 실행되더라.

어떻게 보면 당연한 결과다. 이미 I/O 스레드에서 실행 중인 코드를 다시 컨텍스트만 바꾸려고 하는 건데 될 턱이 없다.

 

순전히 내 Coroutine에 대한 이해도가 낮음이 원인인 트러블이다.

 

이 문제를 통해 처음으로 withContext와 CoroutineScope의 차이를 알게 되었다.

 

withContext: 함수는 코루틴을 현재 컨텍스트에서 다른 컨텍스트로 전환할 사용한다.

withContext 블록의 실행이 완료될 때까지 현재 코루틴의 실행을 일시 중단할 뿐이다.

 

CoroutineScope: 코루틴을 시작할 수 있는 범위를 제공한다.

CoroutineScope.launch나 CoroutineScope.async를 사용하면 새로운 코루틴을 시작할 수 있으며, 이 코루틴은 해당 스코프의 생명주기에 연결된다.

CoroutineScope는 특정한 스레드나 디스패처에 대해 언급하지 않는 한, 기본적으로 현재 컨텍스트(즉, 호출한 스코프)를 사용하여 코루틴을 실행한다.

 

withContext(Dispatcher.IO)를 사용할 때 코루틴은 I/O-optimized 스레드 풀에서 실행되며, 작업이 완료되기 전까지는 호출한 코루틴을 블로킹한다.

반면 CoroutineScope.launch는 새 코루틴을 시작하지만 호출자를 블로킹하지 않는다.

즉, withContext 일종의 스레드 전환과 결과를 기다리는 방식으로 작동하는 반면, launch 새로운 코루틴을 시작하고 곧바로 제어를 반환하여 호출자가 다른 작업을 계속할 있게 한다는 것.

 

이를 토대로 위 코드의 논리 전개를 다음과 같이 수정했다.

suspend fun ApiCall():String{

	runCatching{
    	...
    }.onSuccess{
    	...
        Timber.e("1111")
        // 다른 코드
        CoroutineScope(Dispatcher.IO).launch{
            // DB 갱신 코드
            Timber.e("2222")
        }
        Timber.e("3333")
        ...
    }.onFailure{
    	...
    }
    Timber.e("4444")
    
    return(...)
    
}

 

이제 얼추 내가 원하는 그림이 나왔다.

 

근데 4444는 어디갔을까?

 

runCatching의 onSuccess가 끝나야 호출이 된다.. 이것 조차 당연한 것.

아무튼 해당 부분을 수정하여 코루틴의 작업이 병렬로 처리되도록 할 수 있었다.

 

 


Ref.

 

[Kotlin] 코틀린 공식 문서 - Coroutine context and dispatchers

개요 해당 게시글은 Coroutine context and dispatchers | Kotlin (kotlinlang.org)을 번역한 게시글 입니다. Coroutine context and dispatchers 코루틴은 코틀린 표준 라이브러리에 정의된 Coroutine Context 타입의 값으로 표

bb-library.tistory.com

 

 

The difference between suspend fun doSomething() and fun CoroutineScope.doSomething()

A suspend fun doSomething() guarantees that it will always complete all work before returning. By using the coroutineScope builder it can…

medium.com