Coroutine (μ½λ£¨ν΄)
https://www.kodeco.com/34262147-kotlin-coroutines-tutorial-for-android-advanced
μ°λ λλ₯Ό λ©μΆμ§ μκ³ λΉλκΈ°μ μΌλ‘ μ½λ£¨ν΄ μ€μ½ν λ΄μμ μ§μ ν΄μ€ μν μ μνν¨.
Coroutines Builder
μ μ½λ£¨ν΄μ μμνκ³ μ€ννλ €λ©΄ μ½λ£¨ν΄ λΉλλ₯Ό μ¬μ©ν΄μΌ ν©λλ€. κ·Έλ€μ μΌλΆ μ½λλ₯Ό κ°μ Έμ μ½λ£¨ν΄μΌλ‘ λννμ¬ μ€νμ μν΄ μμ€ν μ μ λ¬ν©λλ€.
μ½λ£¨ν΄μ κΈ°λ³Έ λΉλλ launch()
μ
λλ€. μ μ½λ£¨ν΄μ μμ±νκ³ κΈ°λ³Έμ μΌλ‘ μ¦μ μ€νν©λλ€. μΌλΆ CoroutineScope
컨ν
μ€νΈμμ μ½λ£¨ν΄μ λΉλνκ³ μμν©λλ€.
GlobalScope.launch { // CoroutineScope
// coroutine body
}
μ€μ²©λ μ½λ£¨ν΄μ μμνλ κΈ°ν μΌμ μ€μ§ κ°λ₯ν ν¨μμμ Coroutines Builder
λ₯Ό μ¬μ©ν μ μμ΅λλ€.
λμ μ€ν
λ λ€λ₯Έ μ½λ£¨ν΄ λΉλλ async()
. μ½λ£¨ν΄μμ κ°μ λ°ννλ λ° μ¬μ©ν μ μκΈ° λλ¬Έμ νΉλ³ν©λλ€. μ΄λ κ² νλ©΄ λμ μ€νμ΄ κ°λ₯ν©λλ€. async()
λ€μκ³Ό κ°μ΄ λͺ¨λ μ½λ£¨ν΄μμ μ¬μ©ν μ μμ΅λλ€ .
GlobalScope.launch { // CoroutineScope
val someValue = async { getValue() } // μ½λ£¨ν΄μμ κ³μ°λ κ°
}
κ·Έλ¬λ μμ§ κ°μ μ¬μ©ν μ μμ΅λλ€. λΉμ°¨λ¨ μ·¨μ κ°λ₯ ν¨μ²μΈ async()aλ₯Ό λ°νν©λλ€ . Deferred
κ²°κ³Όλ₯Ό μ»μΌλ €λ©΄ λ₯Ό νΈμΆν΄μΌ ν©λλ€ await()
. κΈ°λ€λ¦¬κΈ° μμνλ©΄ κ³μ°λ κ°μ μ»μ λκΉμ§ λν μ½λ£¨ν΄μ μΌμ μ€μ§ν©λλ€.
μ½κ° μλ€λ₯Έ μ½λ£¨ν΄μ© λ€λ₯Έ λΉλλ₯Ό μ¬μ©ν μ μμ΅λλ€. runBlocking()
μ½λ£¨ν΄μ΄ νΈμΆμ μ°¨λ¨νλλ‘ ν©λλ€.
runBlocking μ λ©μΈ κΈ°λ₯μ΄λ ν μ€νΈμ κ°μ νΉλ³ν μν©μμ JVM μ’ λ£λ₯Ό νΌνκΈ° μν΄ μ€νμ΄ μλ£λ λκΉμ§ μ€λ λλ₯Ό μ°¨λ¨νλ λΉλμ λλ€. μΌλ° Kotlin μ½λ£¨ν΄ μ½λμμλ μ¬μ©νμ§ μμμΌ ν©λλ€.
Kotlin μ½λ£¨ν΄μ μμνκ³ μ€ννλ λ°©λ²μ μ€λͺ νλ €λ©΄ λͺ κ°μ§ λΌμ΄λΈ μ½λ μ€λν«μ μ΄ν΄λ³΄λ κ²μ΄ κ°μ₯ μ’μ΅λλ€.
Kotlin Playground
import kotlinx.coroutines.*
import java.lang.Thread
@OptIn(DelicateCoroutinesApi::class)
fun main() {
GlobalScope.launch { // launch new coroutine in background and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
val sum1 = async { // non blocking sum1
delay(100L)
2 + 2
}
val sum2 = async { // non blocking sum2
delay(500L)
3 + 3
}
println("waiting concurrent sums")
val total = sum1.await() + sum2.await() // execution stops until both sums are calculated
println("Total is: $total")
}
println("Hello,") // main thread continues while coroutine executes
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}
consoleμ νμΈν΄λ³΄λ©΄
Hello,
World!
waiting concurrent sums
Total is: 10
μ΄μ κ°μ΄ λμ€κ²λ©λλ€.
delay()
μμ μ€λν« μ 1μ΄ λμ κΈ°λ₯μ μΌμ μ€λ¨ νλ λ° μ¬μ©νλ Kotlin μ½λ£¨ν΄μ μμν©λλ€ .
Kotlin μ½λ£¨ν΄μ μ€λ λλ₯Ό μ°¨λ¨νμ§ μμΌλ―λ‘ μ½λλ λ λ²μ§Έ println()λͺ
λ Ήλ¬Έ μΌλ‘ μ§ννμ¬ 1) Hello,.
λ€μμΌλ‘ μ½λλ λ©μΈ μ€λ λλ μ μκΈ° λλ¬Έμ μ½λ£¨ν΄μ΄ μ€νμ μλ£νκΈ° μ μ νλ‘κ·Έλ¨μ΄ μλ£λμ§ μμ΅λλ€.
μ½λ£¨ν΄μ λ λ²μ§Έ μ€μ μ€ννκ³ λ₯Ό μΈμν©λλ€ 2) World!.
κ·Έλ° λ€μ λ κ°μ λΉλκΈ° μ½λ£¨ν΄
μ λμμ λΉλνκ³ μμν©λλ€. λ§μ§λ§μΌλ‘ λ λμ μμ
μ΄ λͺ¨λ μλ£λλ©΄ 3) ν©κ³
κ° μΆλ ₯λ©λλ€.
CoroutineScope
μ½λ£¨ν΄μ λ°μΈλ©λλ μλͺ
μ£ΌκΈ° λ°μΈλ© κ΅¬μ± μμλ₯Ό μ 곡νμ¬
μ μ½λ£¨ν΄μ μ νν©λλ€.
λͺ¨λ μ½λ£¨ν΄ λΉλλ CoroutineScope
μ νμ μ μλ νμ₯ ν¨μμ
λλ€.
Android μ±μμλ CoroutineScope
μλͺ
μ£ΌκΈ°κ° μ μ μλ κ΅¬μ± μμλ₯Ό ꡬνν©λλ€.
κ΅¬μ± μμμλ Activity
, Fragment
, ViewModel
Job λ²μκ° μ·¨μλλ©΄ λͺ¨λ Kotlin μ½λ£¨ν΄μ΄ 리μμ€λ₯Ό μ 리νκ³ μ·¨μν©λλ€.
import kotlinx.coroutines.*
import java.lang.Thread
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope { // Creates a new coroutine scope
val job = launch {
println("Task from nested launch, this is printed")
delay(500L)
println("Task from nested launch, this won't be printed")
}
delay(100L)
println("Task from first coroutine scope") // Printed before initial launch
job.cancel() // This cancels nested launch's execution
}
println("Coroutine scope is over") // This is not printed until nested launch completes/is cancelled
}
Task from nested launch, this is printed
Task from first coroutine scope
Coroutine scope is over
Task from runBlocking
consoleμ μ΄ν΄λ³΄λ©΄ λͺ κ°μ§λ₯Ό μ μ μμ΅λλ€.
첫째, μ½λ£¨ν΄μ κ°μ λ‘ μ°¨λ¨νλ―λ‘ μ΄μ μ²λΌ νλ‘κ·Έλ¨μ ν΄λ©΄(μ μκΈ°) μνλ‘ λ νμκ° μμ΅λλ€.
coroutineScope()
λ₯Ό μ¬μ©νμ¬ μ λ²μλ₯Ό λ§λλλ€. launch()
λ₯Ό μ§μ°μν€κΈ° λλ¬Έμ coroutineScope()
μμ ν μ€νλ λκΉμ§ μ€νλμ§ μμ΅λλ€. κ·Έλ¬λ coroutineScope() λ΄μμ μμ
κ³Ό μ€μ²©λ μ½λ£¨ν΄μ μ μ₯νκ³ μ§μ°ν©λλ€.
μ§μ°λ νμ μ·¨μνκΈ° λλ¬Έμ 첫 λ²μ§Έ λ¬Έλ§ μΆλ ₯νκ³
κΆκ·Ήμ μΌλ‘ λ λ²μ§Έ μΆλ ₯ μ μ μ·¨μν©λλ€. κ·Έλ¦¬κ³ coroutineScope()
κ° μλ£λλ©΄
μ΄κΈ° launch()
κ° μ§μ°μ λλ΄κ³ μ€νμ κ³μν μ μμ΅λλ€.
λ§μ§λ§μΌλ‘ λ²μκ° μλ£λλ©΄ runBlocking()
λ μλ£λ μ μμ΅λλ€.
μ΄κ²μΌλ‘ νλ‘κ·Έλ¨μ΄ μ’ λ£λ©λλ€. κ²½ν© μνλ μ€λ¨λ 리μμ€ μμ΄
μμ μ μΈ μ½λ£¨ν΄μ ꡬμΆνλ €λ©΄ μ΄ μ€ν νλ¦μ μ΄ν΄νλ κ²μ΄ μ€μν©λλ€.
μ΄μ μΉμ
μμ μ½λ£¨ν΄ μ€νμ μ·¨μνλ λ°©λ²μ μ΄ν΄λ³΄μμ΅λλ€.
μμ
μ μλͺ
μ£ΌκΈ°κ° μλ μ·¨μ κ°λ₯ν κ΅¬μ± μμμμ μ΄ν΄ν΄μΌ ν©λλ€.
launch()
λ₯Ό νΈμΆνμ¬ μμ±λ©λλ€. Job()
μ μ¬μ©νμ¬ μμ±ν μλ μμ΅λλ€. λ€λ₯Έ μμ
μ κ³μΈ΅ ꡬ쑰 λ΄μμ λΆλͺ¨ λλ μμμΌλ‘ μ΄ μ μμ΅λλ€.
μμ μμ
μ μ·¨μνλ©΄ λͺ¨λ νμ μμ
λ μ·¨μλ©λλ€.
νμ μμ
μ΄ μ€ν¨νκ±°λ μ·¨μλλ©΄ μμ λ° μμ κ³μΈ΅ ꡬ쑰λ μ·¨μλ©λλ€.
κ³μΈ΅μ΄ λ°λ μμΈλ λ¬Όλ‘ CancellationException
μ
λλ€.
μμμ΄ μ€ν¨νλ©΄ κΈ°λ³Έμ μΌλ‘ λΆλͺ¨μ κ³μΈ΅ ꡬ쑰μ λ€λ₯Έ λͺ¨λ μμμ΄ μ·¨μλ©λλ€. λλ‘λ μ½λ£¨ν΄ μ€νμ΄ ν¨κ³Όμ μΌλ‘ μ·¨μλ λκΉμ§ κΈ°λ€λ €μΌ ν©λλ€. μ΄ κ²½μ° job.cancel()
λμ job.cancelAndJoin()
μ νΈμΆν μ μμ΅λλ€.
import java.lang.Thread
import kotlinx.coroutines.*
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancelable computation loop
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
Dispatcherλ μ½λ£¨ν΄μ΄ μ€νμ μν΄ μ¬μ©νλ μ€λ λ λλ μ€λ λ νμ κ²°μ ν©λλ€.
λμ€ν¨μ²λ μ½λ£¨ν΄μ νΉμ μ€λ λλ‘ μ νν μ μμ΅λλ€.
μ€λ λ νλ‘ λ³΄λΌ μλ μμ΅λλ€.
ννμ§λ μμ§λ§ μμΈ‘ν μ μλ νΉμ μ€λ λ© κ·μΉ μμ΄ μ½λ£¨ν΄μ μ ν μμ΄ μ€νν μ μμ΅λλ€.
λ€μμ λͺ κ°μ§ μΌλ°μ μΈ λμ€ν¨μ²μ λλ€.
Dispatchers.Main
μ½λ£¨ν΄μ Swing, JavaFX λλ Android μ±κ³Ό κ°μ UI κΈ°λ° νλ‘κ·Έλ¨μ κΈ°λ³Έ μ€λ λλ‘ μ νν©λλ€. μ΄ λμ€ν¨μ²λ Gradle λλ Mavenμμ νκ²½λ³ Main λμ€ν¨μ² μ’
μμ±μ μΆκ°νμ§ μμΌλ©΄ μλνμ§ μλλ€λ μ μ μ μν΄μΌ ν©λλ€.
UIμ μνΈμμ©νλ μμ
μ μ€ννκΈ° μν΄μλ§ μ¬μ©ν΄μΌν¨
Dispatchers.Default
νμ€ λΉλμμ μ¬μ©νλ κΈ°λ³Έ λμ€ν¨μ²μ
λλ€. JVM μ€λ λμ 곡μ νμ΄ μ§μν©λλ€. CPU μ§μ½μ κ³μ°μ μ΄ λμ€ν¨μ²λ₯Ό μ¬μ©νμμμ€.
CPUλ₯Ό λ§μ΄ μ¬μ©νλ μμ
μ κΈ°λ³Έ μ€λ λ μΈλΆμμ μ€ννλλ‘ μ΅μ ν
Dispatchers.IO
곡μ μ€λ λ νμ μ¬μ©νλ I/O μ§μ€ μ°¨λ¨ μμ
μ μ΄ λμ€ν¨μ²λ₯Ό μ¬μ©ν©λλ€.
λμ€ν¬ λλ λ€νΈμν¬I/O μμ
μ μ€ννλλ° μ΅μ ν
Dispatchers.Unconfined
μ΄ λμ€ν¨μ²λ μ½λ£¨ν΄μ νΉμ μ€λ λλ‘ μ ννμ§ μμ΅λλ€. μ½λ£¨ν΄μ μμ μ νΈμΆν μμλ CoroutineDispatcherμμ μ€νμ μμνμ§λ§ 첫 λ²μ§Έ μ μ§ μ§μ κΉμ§λ§ μ€νλ©λλ€. μΌμ μ€λ¨μ΄ λλλ©΄ νΈμΆλ μΌμ μ€λ¨ ν¨μμ μν΄ μμ ν κ²°μ λ μ€λ λμμ λ€μ μμλ©λλ€.
import java.lang.Thread
import kotlinx.coroutines.*
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking<Unit> {
launch { //context of the parent, main runBlocking coroutine
println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { //not confined -- will inmediatly run in main thread but not after suspension
println("Unconfined: I'm working in thread ${Thread.currentThread().name}")
delay(100L) // delays (suspends) execution 100 ms
println("Unconfined: I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { //will get dispatched to DefaultDispatcher
println("Default: I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) {// will get its own new thread
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
}
Unconfined: I'm working in thread main @coroutine#3
Default: I'm working in thread DefaultDispatcher-worker-1 @coroutine#4
main runBlocking: I'm working in thread main @coroutine#2
newSingleThreadContext: I'm working in thread MyOwnThread @coroutine#5
Unconfined: I'm working in thread kotlinx.coroutines.DefaultExecutor @coroutine#3
μ€λ λμμ ν¬μ°©λμ§ μμ μμΈκ° λ°μνλ©΄ JVMμ μ€λ λμμ UncaughtExceptionHandler
λ₯Ό 쿼리ν©λλ€. κ·Έλ° λ€μ JVMμ μ’
λ£ μ€λ λμ ν¬μ°©λμ§ μμ μμΈλ₯Ό μ λ¬ν©λλ€. μ΄λ μ½λ£¨ν΄κ³Ό Java λμμ±μ΄ λμΌν μμΈ λμμ μ²λ¦¬νκΈ° λλ¬Έμ μ€μν©λλ€.
μ½λ£¨ν΄ λΉλλ λ κ°μ§ μμΈ λ²μ£Όλ‘ λλ©λλ€.
첫 λ²μ§Έλ launch()
μ κ°μ΄ μλμΌλ‘ μ νλλ―λ‘ λμ μΌμ΄ λ°μνλ©΄ κ³§ μκ² λ κ²μ
λλ€. λ λ²μ§Έλ async()
μ κ°μ΄ μ¬μ©μκ° μ²λ¦¬ν μμΈλ₯Ό λ
ΈμΆν©λλ€. κ°μ μ»κΈ° μν΄ await()
λ₯Ό νΈμΆν λκΉμ§ μ νλμ§ μμ΅λλ€.
import java.lang.Thread
import kotlinx.coroutines.*
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking<Unit> {
// propagating exception to the default Thread.UncaughtExceptionHandler
val job = GlobalScope.launch {
// throw AssertionError() -> μ£Όμ ON/OFF Output νμΈ !!!
}
// blocks thread execution until coroutine completes
job.join()
// launches async coroutine but exception is not propagated until await is called
val deferred = GlobalScope.async(Dispatchers.Default) {
throw AssertionError()
}
//defines a specific handler
val handler = CoroutineExceptionHandler { _, exception ->
println("We caught $exception")
}
// propagating exception using a custom CoroutineExceptionHandler
GlobalScope.launch(handler) {
throw AssertionError()
}
// This exception is finally propagated calling await and should be handled by user, eg. with try {} catch {}
deferred.await()
}
# 첫λ²μ§Έ μ€ μ£Όμ νμμ κ²½μ°
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.AssertionError
at FileKt$main$1$job$1.invokeSuspend(File.kt:8)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [CoroutineId(2), "coroutine#2":StandaloneCoroutine{Cancelling}@14d300bf, Dispatchers.Default]
We caught java.lang.AssertionError
Exception in thread "main" java.lang.AssertionError
at FileKt$main$1$deferred$1.invokeSuspend (File.kt:16)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
# 첫λ²μ§Έ μ€ μ£ΌμμΌ κ²½μ°
We caught java.lang.AssertionError
Exception in thread "main" java.lang.AssertionError
at FileKt$main$1$deferred$1.invokeSuspend (File.kt:16)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
Handling μ‘ν consoleμ νμΈ ν μ μλ€.