Software Development/Application Develop

Kotlin-Coroutines

huiyu 2024. 7. 21. 19:45

Coroutines

A coroutine is a concurrency design pattern that you can use on Android to simplify code that excutes asynchronously.

On Android, coroutines help to manage long-running tasks that might otherwise block the main thread and cause your app to become unresponsive.

Coroutines are a Kotlin feature that converts async callbacks for long-runnign tasks, such as database or network access, into sequential code.

Use suspend functions to make async code sequential.

https://kotlinlang.org/docs/coroutines-overview.html#tutorials

  • Basic
    • Coroutine builder
      • launch : 비동기적으로 코루틴을 시작한다는 것을 의미하며, 결과값을 반환하지 않는 Job 객체를 리턴한다.
      • runBlocking : 현재 스레드를 블로킹하면서 코루틴을 실행하고, 코루틴의 결과를 반환한다.
    • Scope
      • CoroutineScope : 코루틴의 실행 범위를 지정하고, 코루틴의 생명 주기를 관리한다.
      • GlobalScope : 프로그램 전체에서 사용할 수 있는 전역 코루틴 스코프로 사용되며, 앱 전체의 생명주기에 종속된다.
    • Suspend function
      • suspend : 해당 함수가 코루틴 또는 다른 suspend 함수 내에서만 호출될 수 있음을 나타낸다.
      • delay() : 지정된 시간 동안 코루틴을 일시 중지한다.
      • join() : 시작된 코루틴이 완료될 때까지 현재 코루틴을 일시 중지한다.
    • Structured concurrency
      • 코루틴은 정의된 스코프 내에서 실행되며, 스코프가 종료되면 모든 코루틴이 정상적으로 종료되도록 구조화된 동시성을 제공한다.

 

  • Cancellation and Timeouts
    • Job
      • cancel() : 실행중인 코루틴을 취소시 사용, 이 메서드는 코루틴을 즉시 종료하지 않는다.
        * "즉시 종료하지 않는다" 
        ->cancel() 함수를 호출하면 해당 코루틴의 작업 취소가 요청되지만, 코루틴이 그 즉시 중단되거나 종료되는 것은 아니다. 코루틴은 협조적 취소(Cooperative cancellation)를 따르기 때문에 코루틴 내부에서 취소 가능 지점을 주기적으로 확인하거나, 취소 상태를 직접 확인해야 한다. 
      • Cancellation is cooperative
        • way 1: to periodically invoke a suspending (call suspend)
        • way 2: explicitly check the cancellation status(isActive)
      • Timeout
        • withTimeout : 지정된 시간이 지나면 TimeoutCancellationException을 던져 코루틴을 취소한다.
        • withTimeoutOrNull : 지정된 시간이 지나면 'null' 을 반환하고 코루틴을 취소하지만 예외는 던지지 않는다.
      • etc
        • yield() : 다른 코루틴에 실행을 양보하고 현재 코루틴이 다음 차례가 올 때 까지 중지한다.
        • withContext(NonCancellable) - in rare case
          : 특정 경우에 코루틴이 취소되지 않도록 보장하며, 일반적으로 취소할 수 없는 작업을 완료해야 할 때 사용된다.
* 코루틴취소 관련
- Suspension Points
 : 'suspend' 함수를 호출하는 지점에서만 취소가 가능, 코루틴 내부에서 긴 계산 작업이나 블록 작업을 수행할 경우엔 주기적으로 'yield()'함수를 호출하거나, 'isActive'속성을 체크하는 것이 좋다. 이런 방법을 통해 코루틴이 취소 요청을 적절히 확인하고 반응할 수 있다.
- 리소스 정리
 : 코루틴이 취소되었을 경우, 열려 있는 파일이나 네트워크 연결 같은 자원을 정리하는 것이 좋다. 이를 위해 'try/finally' 블록을 사용하여 코루틴이 취소되더라도 필수적인 정리 작업이 수행되도록 할 수 있다. 
- NonCancellable
 : withContext(NonCancellable)는 코루틴이 중간에 취소되어서는 안 되는 중요한 정리 작업을 수행할 경우 유용하다. 예를 들어, 거래를 완료하거나 중요 데이터를 저장하는 동안에는 이 컨텍스트를 사용하여 코루틴의 취소를 방지할 수 있다.
- Timeout 처리
 : 'withTimeout' 함수를 사용하면 지정된 시간 안에 작업이 완료되지 않을 경우 'TimeoutCancellationException'을 발생시켜 코루틴을 취소할 수 있다. 작업이 정상적으로 완료되거나 중요한 상황에서 예외 처리를 통해 특정 로직을 실행할 수 있다.

 

val time = measureTimeMillis {
    val one = doSomethingUsefulOne()
    val two = doSomethingUsefulTwo()
    println("The answer is ${one + two}")
}
println("Completed in $time ms")

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

 코루틴을 사용하여 비동기적이지만 순차적인 작업을 수행하는 방법에 대한 예시.
각 함수는 독립적으로 실행되지만, 결과는 순차적으로 계산되며, 메인 스레드의 차단 없이 전체 프로세스가 관리된다. 이는 코루틴이 비동기 작업을 간결하고 효율적으로 처리할 수 있게 해준다

  • Concurrent using async
    • What if there are no dependencies between invocations
    • we want to get the answer faster, by doing both concurrently?
    • This is twice as fast, because the two coroutines execute concurrently.
    • Note that concurrency with coroutines is always explicit.
val time = measureTimeMillis {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")

 'async'를 사용하면 코루틴이 시작되며, 경량 스레드처럼 동작하면서 다른 코루틴과 동시에 실행된다. 'async'는 결과 값을 가질 수 있는 'Deferred'객체를 반환한다. 이 'Deferred'객체를 통해 나중에 결과를 얻을 수 있으며 '.await()' 메소드를 사용해 해당 결과를 비동기적으로 기다릴 수 있다. 이 방법을 사용하면 여러 비동기 작업을 병렬로 처리하고 결과를 쉽게 합칠 수 있어 전체 실행 시간을 단축시킨다. 'async'는 또한 'job'의 한 형태로써, 필요에 따라 작업을 취소할 수 있다.

* Deferred ?
 : Coroutine에서 'async'빌더를 사용할 때 반환되는 특수한 타입. 'Job'의 서브클래스로, 비동기 작업의 결과를 나중에 받을 수 있게 해준다. 'Deferred'는 비동기 작업이 완료될 때까지 결과를 기다리는데 사용할 수 있는 '.await()'메서드를 제공한다.
 - await() 메서드를 통해 비동기 작업의 결과를 요청하고 기다릴 수 있다. 
 - 작업을 기다리는 동안 다른 작업을 차단하지 않는다, 메인 스레드나 다른 중요한 작업을 방해하지 않고 결과를 기다릴 수 있다.
 - cancel()로 취소할 수 있다. 
 - 비동기 작업 중 발생한 예외는 await() 호출 시점에 처리, await()는 작업 중 발생한 예외를 그대로 던지므로, 호출자는 이를 적절히 처리해야 한다.

  • Lazily started async
    • Optionally, async can be made lazy by setting its start parameter
    • its result is required by await, or if its job's start function is invoked
    • if we just call await in println without first calling start
val time = measureTimeMillis {
    val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
    val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
    // some computation
    one.start() // start the first one
    two.start() // start the second one
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")

 

728x90