티스토리 뷰

728x90

 

코루틴

  1. suspend 함수
suspend fun sum(val1: Int, val2: Int): Int {
    delay(1000)
    return val1 + val2
}
 
  • 함수 실행을 중단한 다음, 나중에 필요할 때 다시 실행을 계속 진행할 수 있는 함수
  • 함수에 suspend라는 키워드를 붙인다.
  • 스레드를 block 시키지 않고 일시 중단하며, 그동안 스레드가 다른 작업을 수행할 수 있다.

 

sum(val1: Int, val2: Int, val3: Continuation)
  • 컴파일 시 마지막 파라미터로 Continuation 객체를 추가한다.
  • Continuation을 통해 중단(suspend) 시점의 실행 정보들을 저장하고, 실행이 다시 재개(resume)되면 저장한 정보를 기반으로 실행을 다시 이어나간다.

 

suspend fun main() {
    println("Task started")
    delay(100)
    println("Task finished")
}
 
  • 일반 함수는 suspend 함수를 호출할 수 없고, suspend 함수나 코루틴 안에서 호출할 수 있다.
  • → main()을 suspend 함수로 만들면 호출 가능

 

 

 

       2. 코루틴 빌더

  • suspend 함수를 생성하지 않고도 코루틴 빌더를 통해 일시 중단하고 재개할 수 있는 함수를 호출할 수 있다.

 

  • launch()
fun main() {
    GlobalScope.launch {
        delay(100)
        println("Task finished")
    }
    Thread.sleep(200)
}
 
    • CoroutineScope.() -> Unit 타입의 suspend 람다를 인자로 받는다. (새 코루틴의 본문이 됨)
    • 코루틴을 시작하고 실행중인 작업의 상태를 추적/변경할 수 있는 Job 인스턴스를 반환한다.

 

  • async()
suspend fun main() {
    val msg1 = GlobalScope.async {
        delay(100)
        "Hello "
    }
     
    val msg2 = GlobalScope.async {
        delay(100)
        "World"
    }
     
    val result = msg1.await() + msg2.await()
    println(result) // Hello World
}
 
    • await()를 통해 계산 결과를 얻을 수 있는 Deferred 인스턴스를 반환한다.
    • await()는 해당 코루틴이 완료될 때까지 기다린다.

 

  • runBlocking()
fun main() {
    GlobalScope.launch {
        println("Background task: ${Thread.currentThread().name}") // DefaultDispatcher-worker-1
    }
    runBlocking {
        println("Primary task: ${Thread.currentThread().name}") // main
    }
}
 
    • 현재 스레드에서 실행되는 코루틴을 생성한다.
      • launch(), async()는 스레드 풀 사용
    • 코루틴의 본문이 완료될 때까지 현재 스레드의 실행을 block 시킨다.
    • 블러킹 동작 때문에 다른 코루틴 안에서 사용할 수 없고 최상위에서만 사용해야 한다.

 

 

 

       3. structed concurrency

  • 코루틴끼리의 부모-자식 관계로 코루틴 실행 시간을 제한할 수 있다.
  • 어떤 코루틴을 다른 코루틴에서 실행한다면, 상위 코루틴이 부모/하위 코루틴이 자식이 된다.
  • 자식의 실행이 모두 끝나야 부모가 끝난다.

 

fun main() {
    runBlocking { // 부모 코루틴
        println("Parent task started")
         
        launch { // 자식 코루틴
            println("Task 1 started")
            delay(200)
            println("Task 1 finished")
        }
         
        launch { // 자식 코루틴
            println("Task 2 started")
            delay(200)
            println("Task 2 finished")
        }
         
        delay(100)
        println("Parent task finished")
    }
    println("Shutting down")
}
 
실행 결과
Parent task started
Task 1 started
Task 2 started
Parent task finished
Task 1 finished
Task 2 finished
Shutting down
  • 부모 코루틴이 자식 코루틴보다 일찍 끝나지만, 자식이 끝날 때까지 일시 중단 상태로 기다린다.
  • runBlocking()이 메인 스레드를 block하고 있었기 때문에 부모 스레드가 끝난 후 마지막 메시지가 출력된다

 

 

 

       4. context

fun main() {
    GlobalScope.launch {
        val job = coroutineContext[Job.Key]         // 현재 잡 얻기
        println("Task is active: ${job?.isActive}") // 잡의 상태 출력
    }
}
  • 모든 코루틴은 CoroutineContext 인터페이스의 context 안에서 실행된다.
  • 코루틴 본문 안에서 coroutineContext 프로퍼티를 통해 context에 접근할 수 있다.
  • context는 키-값 쌍으로 이루어진 불변 컬렉션이다.
  • Element들은 각 Key를 기반으로 CoroutineContext에 등록된다.
    • Element → CoroutineId, CoroutineName, CoroutineDispatcher, ContinuationInterceptor, CoroutineExceptionHandler

 

  • CoroutineContext 함수
operator fun <E : Element> get(key: Key<E>): E?
fun <R> fold(initial: R, operation: (R, Element) -> R): R
operator fun plus(context: CoroutineContext): CoroutineContext
fun minusKey(key: Key<*>): CoroutineContext
 
    • get() : 주어진 key에 해당하는 context element를 반환한다.
    • fold() : context의 element들에 특정한 함수를 수행하여 결과를 반환한다.
    • plus() : 현재 context와 인자로 주어진 다른 context의 요소를 모두 포함하는 context를 반환한다. 즉, context끼리 합쳐주는 역할을 한다.
    • minusKey() : 현재 context에서 인자로 주어진 key를 제외한 새로운 context를 반환한다.

 

  • CoroutineContext 구현체
    • EmptyCoroutineContext: context가 명시되지 않을 경우 default로 사용되는 context
    • CombinedContext: 두개 이상의 context가 명시될 경우 context를 연결해준다. 
    • Element: context의 각 element들

  • 코루틴 빌더를 통해 인자로 어떤 context를 넘기는지에 따라 context의 상태가 달라진다.
  • CoroutineContext에 plus operator 함수가 있기 때문에, 각 element들을 +를 통해서 연결할 수 있다.
  • CombinedContext를 통해 Element끼리 묶어 하나의 CoroutineContext가 된다. 

 

fun CoroutineScope.showName() {
    println(coroutineContext[CoroutineName]?.name)
}

fun main() {
    runBlocking {
        showName() // null
         
        launch() {
            showName() // null (현재 context 이어받음)
        }
         
        launch(coroutineContext + CoroutineName("Worker")) { // 생성한 context을 코루틴에 넘김
            showName() // Worker
        }
    }
}
  • 코루틴 빌더에 의해 만들어진 코루틴은 현재 context를 이어받는다.
  • 빌더 함수에 context 파라미터를 지정해서 새 context를 넘길 수 있다.

 

  • withContext()
fun CoroutineScope.printName() = println(Thread.currentThread().name)

fun main() {
    newSingleThreadContext("Worker").use { worker ->
        runBlocking {
            printName() // main
            withContext(worker) { printName() } // Worker
            printName() // main
        }
    }
}
    • context를 전환하여 코루틴을 수행할 수 있게 해준다.

 

 

 

       5. CoroutineScope

interface CoroutineScope {
    val coroutineContext: CoroutineContext
}
  • CoroutineContext 하나만 가지고 있는 인터페이스
  • 모든 코루틴 빌더는 CoroutineScope의 확장 함수다.
  • 새로운 코루틴의 범위를 지정한다.

 

class MyActivity : AppCompatActivity(), CoroutineScope {
    lateinit var job: Job
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
    }
   
    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

    fun loadDataFromUI() = launch {
        val ioData = async(Dispatchers.IO) { ... }

        val data = ioData.await()
        draw(data)
    }
}
  • CoroutineScope를 상속받고 coroutineContext를 override한다.
  • 클래스 내에서 만든 코루틴은 메인 스레드와 Job 객체를 context로 갖는다.

 

  • GlobalScope
object GlobalScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}
 
    • EmptyCoroutineContext를 context로 가지고 있다.
    • 애플리케이션의 생명주기 동안 작동하는 코루틴을 만들 수 있다.

 

  • coroutineScope()
 
fun main() {
    runBlocking {
        println("Parent task started")
         
        coroutineScope {
            launch {
                println("Task 1 started")
                delay(200)
                println("Task 1 finished")
            }
         
            launch {
                println("Task 2 started")
                delay(200)
                println("Task 2 finished")
            }
        }
         
        println("Parent task finished")
    }
}
실행 결과
Parent task started
Task 1 started
Task 2 started
Task 1 finished
Task 2 finished
Parent task finished
    • 사용자가 직접 코루틴의 스코프를 지정할 수 있다.
    • runBlocking()과 마찬가지로 람다의 결과를 반환하고, 자식들이 완료되기 전까지 기다린다.
    • coroutineScope()는 suspend 함수이기 때문에 현재 스레드를 block 시키지 않는다.
    • 자식 코루틴 실행이 끝날 때까지 corouineScope()가 일시 중단되기 때문에 마지막 문장이 가장 마지막에 출력된다.

 

 

 

728x90
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함