본문 바로가기
Android/Coroutine

[Android] Coroutine Dispatcher에 대한 고찰 - Deep Dive

by 태크민 2025. 1. 27.

우리는 Kotlin의 흐름과 몇 가지 기본 개념의 내부 작동을 탐구했지만, 코루틴 디스패처에 대해서는 깊이 파고든 적이 없습니다. 이번 포스팅을 통해 Coroutine DisPatcher에 대해 깊게 알아보는 시간을 갖고자 합니다.

이를 분석해서 이것이 코드에 어떤 영향을 미치는지 살펴보겠습니다.

 

Dispatcher란?

Dispatcher의 사전적인 의미는 사람이나 차량을 필요한 곳으로 보내는 일을 담당하는 사람입니다.

Kotlin 코루틴에서의 DispatcherCoroutine들을 적절하게 쓰레드 또는 쓰레드 풀에 할당하여 실행시키는 Scheduler 역할을 하는 코루틴 Cntext의 일부라고 할 수 있습니다. 

Dispactcher의 종류로는 Default / IO / Main / Unconfinied가 있습니다.

 

그렇다면 이렇게 설정한 디스패쳐들은 구체적으로 마구 쏟아져 들어오는 코루틴들의 실행을 어떻게 스케줄링 할까요?

 

How to scheduling?

사전 정의 된 디스패쳐들 중에서 Default  IO 디스패쳐에 대해 중점적으로 알아보겠습니다.

(Main 디스패쳐는 애플리케이션 메인 스레드(single thread)에서 EventLoop 를 이용해 코루틴의 실행을 스케쥴링하고,

Unconfined 는 코루틴(Continuation)이 재개(suspend->resume) 되는 스레드에서 바로 실행하므로 상대적으로 이해가 간단합니다.)

 

코루틴 JVM 에서 Dispatchers 의 내부 구현을 살펴보면, Dispatchers.Default  Dispatchers.IO  CoroutineScheduler 라는 동일한 스케쥴러를 공유합니다.

 

코루틴들은 디스패쳐를 통해 CoroutineScheduler 로 요청 될 때 Task 라는 형태로 래핑되어 요청됩니다. 이 때, Dispatchers.Default 디스패쳐를 사용하도록 설정 된 코루틴은 NonBlockingContext 으로 표시되어 내부적으로 CPU intensive 한 작업들을 위한 큐를 이용하여 처리되고, Dispatchers.IO 디스패쳐를 사용하도록 설정 된 코루틴은 Task 에 ProbablyBlockingContext 로 표시되어 내부적으로 I/O intensive 한 작업들을 위한 큐를 이용하여 처리 됩니다.

 

이들의 관계를 그려보면 다음처럼 그려볼 수 있을 것 같습니다.

 

우리는 상대적으로 CPU 연산이 주를 이루는 코루틴(초록색 코드 블럭을 갖는)과 I/O 작업이 주를 이루는 코루틴(보라색 코드 블럭을 갖는)을 요청하고자 합니다.
이 때, 우리는 코루틴이 사용할 디스패쳐로 각각 Dispatcher.Default  Dispatchers.IO 를 설정해 주게 됩니다.

 

Default 디스패쳐의 경우 바로 CoroutineScheduler 에 수행하고자 하는 작업을 Task 형식으로 래핑하여 스케쥴링을 요청하게 되고, IO 디스패쳐의 경우 LimitingDispatcher 라는 클래스로 래핑되어 자체적으로 설정 된 병렬 실행 수 제한치(parallelism limit)에 따라 실제 CoroutineScheduler 로 스케쥴링 요청을 할 지, 아니면 자체적으로 갖는 작업 큐에 작업을 대기 시킬지 결정하게 됩니다. 즉, I/O Task 의 동시 수행 수는 이 LimitingDispatcher 를 통해 조절 됩니다.

(Limiting에 대해서는 아래에서 설명 예정입니다.)

 

위의 이미지에서

  • globalCpuQueue는 Dispatcher.Default가 활용하는 큐
  • globalBlockingQueue는 Dispatcher.IO가 활용하는 큐

를 의미합니다.

 

결국, 코루틴은 디스패처(Dispatcher)를 통해 스케줄러 내부로 진입하며, 스케줄러는 작업 큐(Task Queue)의 형태로 각 코루틴을 관리하고 있다 정도로 이해하시면 됩니다.

 

자 이제 코루틴에서 제공하는 Dispatcher 4가지에 대해 자세히 알아보겠습니다.

 


 

Dispatchers.Default

Dispatcher.Default코루틴의 기본 디스패처로, 코루틴 Scope에 특정 디스패처를 할당하지 않으면 기본적으로 이를 사용합니다.

※ Note

단, runBlocking은 내부적으로 자체 Dispatcher를 사용하며, 기본적으로 자신을 호출한 스레드를 기본 디스패처로 사용합니다. 또한, ViewModelScope의 기본 디스패처는 Dispatcher.Mian입니다.

 

Dispatcher.DefaultCPU-intensive 작업을 위해 설계되었습니다.

CPU 코어 수와 동일한 개수의 스레드 풀을 사용하며, 최소 설정 값은 2개입니다. 

이론적으로, 스레드를 효율적으로 사용(CPU-intensive한 non-blocking 연산 등)한다고 가정했을 때, 이는 최적의 스레드 수입니다.

예를들어, 8 코어의 CPU를 가지고 있다면 Dispatcher.Default는 최대 8개의 스레드를 가져 각 코어에서 병렬로 Task를 처리할 수 있습니다

 

아래 코드로 이해를 해보죠.

suspend fun main(): Unit = coroutineScope {

    println(Runtime.getRuntime().availableProcessors())
    
    launch {
        printCoroutinesTime(Dispatchers.Default)
    }
}

private suspend fun printCoroutinesTime(
    dispatcher: CoroutineDispatcher
) {
     val test = measureTimeMillis {
         coroutineScope {
             repeat(10) {
                 launch(dispatcher) {
                     Thread.sleep(1000)
                 }
             }
         }
     }
     println("#1 $dispatcher took: $test")
}

Output:-

10
#1 Dispatchers.Default took: 1010

 

출력에서 볼 수 있듯이 사용가능한 프로세서는 10개입니다.

그래서 10개 작업을 병렬로 실행하여 1초 이내에 모두 완료한 것입니다.

 

하지만 그보다 더 많은 작업을 실행하려고 하면 어떻게 될까요?

suspend fun main(): Unit = coroutineScope {

    println(Runtime.getRuntime().availableProcessors())
    
    launch {
        printCoroutinesTime(Dispatchers.Default)
    }
}

private suspend fun printCoroutinesTime(
    dispatcher: CoroutineDispatcher
) {    
     val test = measureTimeMillis {
         coroutineScope {
             repeat(12) {
                 launch(dispatcher) {
                     Thread.sleep(1000)
                 }
             }
         }
     }
     println("#1 $dispatcher took: $test")
}

Output:-

10
#1 Dispatchers.Default took: 2009

 

1초 이상의 시간이 소요되는 것을 확인할 수 있습니다.

이는 작업 수가 사용 가능한 스레드를 초과했기 때문입니다.

12개의 작업을 처리하려고할 때 처음 10개 작업을 완료한 후 나머지 2개 작업을 처리하게 됩니다. 그래서 총 2초가 걸리는 것입니다.

 

Limiting the default dispatcher

이제 무거운 작업이 발생하여 Dispatchers.Default로부터 모든 스레드를 독점한다고 가정해봅시다. 이 경우 같은 디스패처를 사용하는 다른 코루틴들을 스레드를 사용 하지못하고 starve하게 되며, 무한 딜레이가 될 것입니다.

 

그렇다면 어떻게 이를 해결할 수 있을까요 ?

 

우리는 디스패처 내 스레드의 개수를 limitedParallelism를 통해 제한할 수 있습니다.

 

 limitedParallelsim을 사용하면 특정 프로세스에 할당된 스레드 수를 제한할 수 있습니다. 해당 메소드를 통해 단일 프로세스가 너무 많은 스레드를 독점하는 것을 방지합니다.

Dispatchers.Default의 limitedParallelism는 CPU 코어의 수보다 더 크게 설정할 수는 없으며, 최소 2개는 설정해야 합니다.

 

이러한 이유로 Dispatchers.Default는 CPU-Intensive한 작업을 처리할 때만 사용해야하며, Blocking한 작업을 처리할 때 사용해서는 안됩니다.

 

이것이 실제로 어떻게 이루어지는지 코드를 살펴보겠습니다.

suspend fun main(): Unit = coroutineScope {

    launch {
        val dispatcher = Dispatchers.Default
            .limitedParallelism(6)
        printCoroutinesTime(dispatcher)
    }
}

private suspend fun printCoroutinesTime(
    dispatcher: CoroutineDispatcher
) {
    val test = measureTimeMillis {
        coroutineScope {
            repeat(7) {
                launch(dispatcher) {
                    Thread.sleep(1000)
                }
            }
        }
    }
    println("#1 $dispatcher took: $test")
}

Output:-
10
#1 LimitedDispatcher@26f3706c took: 2015

 

10개의 CPU 코어가 이용 가능하다고 가정해보겠습니다.

위 코드와 같이 6개의 스레드만을 사용 가능하도록 제한하면, 한번에 6개 보다 많은 스레드를 실행하는 것이 불가능하게 됩니다.

따라서, 7번째 작업부터는 작업중인 스레드가 끝날 때 까지 기다려야만 하고, 모두 완료하는데 1초 이상이 걸리게 되는 것입니다.

 

만약 limitedParallelism을 CPU 코어의 개수 보다 많이 설정하면 어떻게 될까요?

limitedParallelism은 Dispatcher.IO에서는 완전히 다른 개념을 가지고 있습니다.
Dispatchers.Default에서는 CPU 코어의 개수 이하만큼만 제한 설정이 가능합니다.
만약 코어의 개수 보다 큰 값으로  limit을 설정한다고 하면 무시되며, 실제 코어 개수만큼의 사이즈로 limit이 설정됩니다.

 

코드로 잠깐 살펴 보도록 하겠습니다.

suspend fun main(): Unit = coroutineScope {
    println(Runtime.getRuntime().availableProcessors())
    launch {
        val dispatcher = Dispatchers.Default
            .limitedParallelism(11)
        printCoroutinesTime(dispatcher)
    }
}

private suspend fun printCoroutinesTime(
    dispatcher: CoroutineDispatcher
) {
    val test = measureTimeMillis {
        coroutineScope {
            repeat(11) {
                launch(dispatcher) {
                    Thread.sleep(1000)
                }
            }
        }
    }
    println("#1 $dispatcher took: $test")
}

Output:- 

10
#1 LimitedDispatcher@491a53c6 took: 2011

 

사용 가능한 CPU 코어 보다 더 높은 제한을 설정하려고 시도한 결과 작업이 1초 이상이 걸렸습니다.

limitedParallelism을 11로 설정했지만, 결국 CPU 코어의 실제 개수인 10개로 제한이 설정된 것입니다.

 

왜 Default Dispatcher는 Blocking 작업에 사용하면 안될까요?

최대 CPU 코어의 개수만큼의 스레드 수가 정해져있기 때문입니다. Dispatchers.Default를 통해 Blocking한 작업(오래 걸릴 수 있는 작업)에 사용하면 해당 스레드가 묶여서 실행해야 하는 다른 코루틴이 사용못할 가능성이 있습니다. 이로 인해 상당한 지연이 발생할 수 있습니다.

 

그렇다면 I/O 작업을 하고 싶은 경우에는 어떻게 할 수 있을까?

 

해결책으로 I/O Dispatcher가 등장하게 됩니다.

 


 

IO Dispatcher

IO Dispatcher는 파일 읽기, 네트워크 요청, 데이터베이스 엑세스와 같이 Blocking I/O 작업을 처리하도록 설계되었습니다. 최소 64개의 스레드 제한이 있어 동시 IO작업에 최적화 되어 있습니다.

 

IO Dispatcher를 사용하면 다른 코루틴을 starve시키거나 성능 병목 현상을 일으킬 걱정 없이 여러개의 Blocking IO 작업을 실행할 수 있습니다. 외부 리소스를 기다려야하는 작업에 적합하지만, CPU Intensive 작업을 위한 것은 아닙니다.

 

아래 코드로 이해를 해보죠.

suspend fun main(): Unit = coroutineScope {
    launch {
        val dispatcher = Dispatchers.IO
        printCoroutinesTime(dispatcher)
    }
}

private suspend fun printCoroutinesTime(
    dispatcher: CoroutineDispatcher
) {
    val test = measureTimeMillis {
        coroutineScope {
            repeat(50) {
                launch(dispatcher) {
                    Thread.sleep(1000)
                }
            }
        }
    }
    println("#1 $dispatcher took: $test")
}


Output:-
#1 Dispatchers.IO took: 1013

 

작업은 약 1초가 걸리는데, Dispatcher.IO는 동시에 실행할 수 있는 활성 스레드를 50개 이상의 지원하기 때문입니다. 

병렬 실행을 위한 높은 용량은 이상적인 I/O 동작을 만들며, 오랫동안 실행되는 작업이 다른 코루틴을 방해 하지 않도록 보장합니다.

 

IO Dispatcher의 내부를 살펴보면 스레드를 할당하는데 UnlimitedIoScheduler를 사용한다는 것을 알 수 있습니다.

 

IO Dispatcher는 제한 없는 스레드 풀 크기를 가지고 있으며, 초기 상한은 64개의 스레드로 설정됩니다.

 

자 여기서 알아야 할 중요한 사실이 있습니다.

 

Default 디스패처와 IO 디스패처는 공통 스레드 풀을 공유합니다. 따라서, 작업 간 스레드를 변경하기 위한 디스패치(dispatching)가 자주 필요하지 않습니다.

예를들어, Dispatcher.Default에서 작업을 실행한 다음 IO 디스패처로 전환하는 경우 동일한 스레드가 사용될 가능성이 높습니다.

 

여기서 또 다른 중요한 차이가 있는데, 스레드 수 제한 입니다.

IO 디스패처로 전환하면 Dispatchers.Default 제한 대신 IO 디스패처의 제한에 적용되게 됩니다.

 

하지만 이들의 제한은 독립적으로 작동하며, 한쪽에서 스레드를 많이 사용한다고 해서 다른 쪽의 작업이 방해받거나 멈추는 일이 발생하지 않습니다.

 

아래 예시를 보면,

suspend fun main(): Unit = coroutineScope {
    launch(Dispatchers.Default) {
        println(Thread.currentThread().name)
        withContext(Dispatchers.IO) {
            println(Thread.currentThread().name)
        }
    }
}

Output:-
DefaultDispatcher-worker-1
DefaultDispatcher-worker-1

 

같은 스레드가 유지된 것을 확인할 수 있습니다.

 

하지만 만약 Dispatchers.Default와 Dispatchers.IO를 최대로 사용한다면 어떻게 될까요?

활성 스레드의 수는 제한의 합이 될 것입니다.

 

예를들어, Dispatcher.IO에서 64개의 스레드를 허용하고 8개의 코어를 가지고 있다면 공유하는 스레드 풀에 72개의 활성 스레드가 존재할 것입니다. 

 

즉, 우리는 각 디스패처를 독립적으로 사용할 수 있고, 효율적으로 스레드를 재사용할 수 있게 됩니다.

 

그러나 문제는 너무 많은 스레드를 할당하면 64개가 넘어가게 되고, 다른 코루틴이 대기하는 현상이 발생할 수 있습니다.

이 문제를 해결하기 위해 우리는 다시 limitedParallelism를 사용합니다.

 

Limiting the default dispatcher

Dispatcher.IO는 Dispatcher.Default와 매우 큰 차이가 있습니다. 

limitedParallelsim을 설정할 때 최대 제한없이 설정이 가능합니다. 즉, 스레드풀의 사이즈를 64개보다 '작게' 또는 '크게' 설정할 수 있습니다. 이것이 가능한 이유는 Dispatcher.IO는 unlimited한 스레드 풀을 가지고 있기 때문입니다.

suspend fun main(): Unit = coroutineScope {
    launch {
        val dispatcher = Dispatchers.IO
            .limitedParallelism(100)
        printCoroutinesTime(dispatcher)
    }
}

private suspend fun printCoroutinesTime(
    dispatcher: CoroutineDispatcher
) {
    val test = measureTimeMillis {
        coroutineScope {
            repeat(100) {
                launch(dispatcher) {
                    Thread.sleep(1000)
                }
            }
        }
    }
    println("#1 $dispatcher took: $test")
}

Output:-
#1 LimitedDispatcher@32a1006c took: 1017

 

스레드 풀의 크기 100개로 설정한 예시이며, 실제로 100개의 작업을 1초만에 끝낸 것을 확인할 수 있습니다.

 

그럼 IO Dispatcher에서 limitedParallelsim의 값을 크게 할 수록 좋은거 아닌가요?

IO Dispatcher에서 limitedParallelism 값을 크게 설정한다고 항상 성능이 좋아지는 것은 아닙니다.

limitedParallelism 값을 크게 설정하면 디스패처가 더 많은 스레드를 사용해 병렬 처리를 시도합니다.

이로 인해 과도한 스레드 생성이 발생하여 잦은 ContextSwitching으로 오히려 성능 저하가 발생할 수 있습니다.

한, 시스템 메모리를 고갈시켜 Out of Memory(OOM) 에러를 유발할 수 있고, 병렬 처리가 지나치게 많아지면 공유 자원에 대한 경쟁 상태(Race Condition)이 발생할 수 있습니다. 이로 인해 락(Lock)을 사용하는 동기화 코드에서 심각한 병목 현상이 초래될 가능성이 높습니다.

결과적으로, 무조건적인 병렬 처리 증가는 오히려 성능 저하로 이어질 수 있습니다.

 

Dispatchers.IO VS Dispatchers.Default

Dispatchers.Default와 Dispatchers.IO는 각각 고유한 스레드 풀에서 동작하며, 병렬 처리 제한이 적용됩니다.

 

Dispatchers.Default

  • 제한된 병렬 처리를 설정하면, 기존 디스패처에 병렬 처리 제한을 추가하여 동작합니다.
  • 즉, Dispatchers.Default에서 생성된 제한된 디스패처는 원래의 디스패처 제한을 상속받으며, 그 안에서 추가적인 제한을 적용하여 동작합니다.
  •  

Dispatchers.IO

  • 제한된 병렬 처리를 사용할 경우, 기존 Dispatchers.IO와는 독립적으로 동작하는 새로운 디스패처가 생성됩니다.
  • 이 새로운 디스패처는 Dispatchers.IO와 같은 무제한 스레드 풀을 공유하지만, 병렬 처리 제한은 독립적으로 적용됩니다.

 

왜 IO Dispatcher는 CPU Intensive 작업을 처리하면 안되는가?

CPU 집중 작업에서는 스레드 풀이 너무 커지면 CPU가 오히려 비효율적으로 동작합니다.

스레드가 너무 많아지면(기본 64개 이상), CPU가 이 스레드들을 전환하는 데 시간을 낭비하기 때문입니다.

 

예를 들어, CPU 코어가 8개이고, 8개의 스레드만 사용하면 되는데 64개의 스레드가 있다고 하면 컨텍스트 스위칭이 빈번하게 일어 날 것입니다. 결과적으로 CPU가 실제 연산보다는 스레드 전환에 더 많은 시간을 쓰게되어 성능이 저하됩니다.

 


 

Main Dispatcher

Android나 많은 애플리케이션 frameworks 들은 main or UI thread의 개념을 갖습니다. 이는 일반적으로 가장 중요한 thread 입니다.

Android에서 유일하게 UI와 interact 하는 thread입니다.
따라서, 이는 매우 자주 사용되며, 주의해서 사용해야 합니다.

오래걸리는 I/O 연산(큰 파일들 읽기 등)이나 blocking functions을 Main Dispatcher에서 수행하게 되면, Main thread가 block되고, 전체 애플리케이션이 frozen 됩니다.
코루틴을 Main thread에서 실행하려면, Dispatchers.Main을 사용합니다.

 

다음은 Dispatcher.Main를 통한 코루틴 예제입니다.

fun main() {
    CoroutineScope(Dispatchers.Main).launch {
        println(1)
    }
    println(2)
}

 

코드를 실행해보면 2가 출력되고 1이 출력됩니다.

 

왜 그럴까요?

 

이는 Main  뿐만 아니라, IO, Default도 동일한 결과가 나오는데 (Unconfinied는 즉시 실행) , 

코루틴이 실행되면 작업 큐에 등록되고, 이후 실행해야 할 시간이 되었다면 큐에서 코루틴이 실행되면서 비동기로 작동되기 때문입니다.

그래서 즉시 실행을 보장하지 않습니다.

 

즉, 작업 큐에 등록된 작업은 현재 실행 중인 동기 작업이 끝난 이후에 실행됩니다. 왜냐하면 현재 Main 스레드가 사용중이기 때문입니다. 그래서 Main스레드가 실행중인 동기작업이 끝나면, 루퍼가 작업 큐에 있는 코루틴을 선택하여 실행하게 되는 원리입니다.

하지만, 만약 Main 스레드를 통해 네트워크 또는 파일 입출력(I/O)과 같이 시간이 오래 걸리는 작업이 큐에 많이 쌓이게 되면, UI 업데이트가 지연되어 ANR이 발생할 수 있습니다. 

따라서, Dispatcher.Main은 UI 업데이트와 같이 가벼운 작업에만 사용을 해야합니다.

 

하지만, 우리는 UI 업데이트와 같이 즉시적으로 처리하고 싶을 때가 있습니다.

여기서 Dispatcher.Main.immediate가 등장합니다.

 

Despatcher.Main.immediate

Dispatchers.Main.immediate 을 사용한다면  이미 해당 코루틴이 메인 스레드에 있다는 것을 의미하고, Dispatcher.Main의 작업큐로 이동하지 않고, 즉시 동기로 실행됩니다.

 

다시 아래 코드를 실행해보면,

fun main() {
    CoroutineScope(Dispatchers.Main.immediate).launch {
        println(1)
    }
    println(2)
}

 

디스패처가 필요하지 않아 동기로 작동되어 1, 2가 차례대로 출력됩니다.

 

디스패처는 isDispatchNeeded() 메서드를 제공하여 디스패치가 발생해야 하는지 여부를 먼저 결정하게 됩니다. 디스패치를 ​​건너뛸 경우 해당 코루틴은 메시지 큐를 건너뛰고 즉시 실행됩니다.

Dispatchers.Main.immediate는 메인 스레드에 있는 동안 isDispatchNeeded()에 대해 false를 반환하므로, 코루틴은 메시지 큐에 전송되지 않고 순차적으로 실행됩니다.

 

Android에서 Dispatchers.Main.immediate 는 viewModelScope 와 lifecycleScope 에 기본 값으로 사용되고 있습니다.

 


 

Unconfined Dispatcher

Dispatchers.Unconfined는 다른 dispatcher들과 달리 어떤 thread도 변경하지 않습니다.
 dispatcher의 코루틴이 시작되면 시작한 thread에서 실행되고, resume 되면, resume thread에서 실행됩니다.

 

Performance 관점에서, 이 dispatcher는 가장 저렴합니다. thread switching이 결코 요구되지 않기 때문입니다. 만약 우리의 코드가 어떤 thread에서 실행되어도 상관이 없다면, 이 dispatcher를 고려해 볼 만합니다.
하지만 실제로는 무분별하게 사용해서는 안됩니다.
Main thread에서 blocking call을 놓친다면 전체 애플리케이션이 freeze 되는 사고로 이어질 수 있겠죠.

 


참고자료

https://proandroiddev.com/dispatchers-io-and-default-under-the-hood-b39aee24d2e9

 

Dispatchers - IO and Default Under the Hood.

Hey folks,

proandroiddev.com

https://medium.com/@wind.orca.pe/dispatchers-kotlin-coroutines-659a5681f329

 

Dispatchers — Kotlin Coroutines

Korean recap

medium.com

 

https://myungpyo.medium.com/%EC%BD%94%EB%A3%A8%ED%8B%B4-%EB%94%94%EC%8A%A4%ED%8C%A8%EC%B3%90-%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0-92db58efca24

 

코루틴 디스패쳐 조금 더 살펴보기

Deeper look into coroutine’s dispatchers

myungpyo.medium.com

https://medium.com/@trionkidnapper/launching-a-kotlin-coroutine-for-immediate-execution-on-the-main-thread-8555e701163b

 

Launching a Kotlin Coroutine for immediate execution on the Main thread

If you launch a coroutine using launch(Dispatchers.Main) while already on the main thread, will the code execute immediately?

medium.com

https://proandroiddev.com/exploring-kotlin-coroutines-dispatchers-a-look-at-dispatchers-main-immediate-78f4991461eb

 

Exploring Kotlin Coroutines Dispatchers: A Look at Dispatchers.Main.immediate

So today, I would like to lead you deep into Kotlin Coroutines Dispatchers myself through Dispatchers.Main.immediate as a prominent…

proandroiddev.com

https://velog.io/@murjune/Kotlin-Coroutine-%EB%94%94%EC%8A%A4%ED%8C%A8%EC%B2%98-%EC%A7%80%EC%A0%95-%EC%95%88%ED%95%98%EB%A9%B4-%EC%95%B1%EC%9D%B4-%EC%A3%BD%EC%9D%84%EC%88%98%EB%8F%84

 

Kotlin Coroutine: 코루틴을 써도 ANR이 발생할 수 있다고?

코루틴 사용하다 ANR 맛 본 사람 드루와~

velog.io

https://todaycode.tistory.com/182

 

Coroutine Dispatcher, 넌 대체 뭐야?

🚀 글 읽는 순서 🚀 코루틴은 왜 빠른 걸까요? suspend 함수란 무엇인가요? -> Coroutine Dispatcher, 넌 대체 뭐야? (현재) 1. 요약 2. Dispatcher 2-1. 개념 2-2. 종류 2-3. 사용법 3. Default와 IO의 차이 4. 의문점 1.

todaycode.tistory.com