[android] Corutine

Corutine

다음 두가지 문제를 해결하는데 코루틴은 도움이 됩니다.

장기 실행 작업 관리

Android의 모든 앱에는 사용자 인터페이스를 처리하고 사용자 상호작용을 관리하는 기본 스레드가 있습니다. 기본 스레드에 너무 많은 작업을 할당한 앱은은 정지된 것처럼 보이거나 매우 느려질 수 있습니다. 네트워크 요청, JSON 파싱, 데이터베이스 읽기 또는 쓰기, 큰 목록 반복 등으로 앱이 느리게 실행되어 버벅거림(터치 이벤트에 UI가 느리게 반응하거나 멈춰 보이는 현상)이 나타날 수 있습니다. 이러한 장기 실행 작업은 기본 스레드 외부에서 실행해야 합니다.

다음 예는 가상 장기 실행 작업에 대한 간단한 코루틴 구현을 보여줍니다.

suspend fun fetchDocs() {                             // Dispatchers.Main
        val result = get("https://developer.android.com") // Dispatchers.IO for `get`
        show(result)                                      // Dispatchers.Main
    }

    suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }
    

코루틴은 장기 실행 작업을 처리하는 두 작업을 추가하여 일반 함수를 기반으로 빌드됩니다. invoke(또는 call ) 및 return 외에도 코루틴은 suspendresume을 추가합니다.

위의 예에서 get()은 여전히 기본 스레드에서 실행되지만 네트워크 요청을 시작하기 전에 코루틴을 정지합니다. 네트워크 요청이 완료되면 get은 콜백을 사용하여 기본 스레드에 알리는 대신 정지된 코루틴을 재개합니다.

기본 안전을 위해 코루틴 사용

Kotlin 코루틴은 디스패처를 사용하여 쿠리틴에 실행에 사용되는 스레드를 결정합니다. 코드를 기본 스레드 외부에서 실행하려면 기본 또는 IO 디스패처에서 작업을 실행하도록 Kotlin 코루틴에 지시하면 됩니다. Kotlin에서 모든 코루틴은 기본 스레드에서 실행 중인 경우에도 디스패처에서 실행되어야 합니다. 코루틴이 정지될 수 있으며 디스패처는 코루틴을 계속해야 합니다.

Kotlin은 코루틴을 실행할 위치를 지정하는데 사용하는 세가지 디스패처를 제공합니다.

withContext()의 성능

withContext()는 상응하는 콜백 기반 구현에 비해 오버헤드를 추가하지 않습니다. 또한 일부 상황에서 상응하는 콜백 기반 구현을 능가하도록 withContext() 호출을 최적화할 수 있습니다. 예를 들어, 함수가 네트워크를 10회 호출하는 경우 외부 withContext()를 사용하여 스레드를 한 번만 전환하도록 Kotlin에 지시할 수 있습니다. 그러면 네트워크 라이브러리에서 withContext()를 여러 번 사용하더라도 동일한 디스패처에 유지되고 스레드가 전환되지 않습니다. 또한 Kotlin은 가능한 스레드 전환을 방지하도록 Dispatchers.DefaultDispatchers.IO 간의 전환을 최적화합니다.

CoroutineScope 지정

코루틴을 정의할 때 CoroutineScope도 함께 지정해야 합니다. CoroutineScope는 하나 이상의 관련 코루틴을 관리합니다. CoroutineScope를 사용하여 이 범위 내에서 새 코루틴을 시작할 수도 있습니다. 하지만 디스패처와 달리 CoroutineScope는 코루틴을 실행하지 않습니다.

CoroutineScope의 한 가지 중요한 기능으로, 사용자가 앱 내의 콘텐츠 영역을 벗어나면 코루틴 실행을 중지합니다. CoroutineScope를 사용하여 실행 중인 작업이 올바르게 중지되는지 확인할 수 있습니다.


import kotlinx.coroutines.* 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 
} 
                                        println("Hello,") // main thread continues while coroutine is delayed 
                                        Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive }


output

“Hello World”

fun test2() { 
  runBlocking<Unit> { 
    launch { delay(1000L) 
            Log.e(TAG, "World") 
           } 
    Log.e(TAG, "Hello") delay(2000L) 
  } 
  Log.e(TAG, "End function") 
}

output

Hello

World

End function

fun main() = runBlocking { 
  val job = GlobalScope.launch { // launch new coroutine and keep a reference to its Job 
    delay(1000L)
    println("World!") 
  } 
  println("Hello,") 
  job.join() // wait until child coroutine completes 
}
fun main() = runBlocking { // this: CoroutineScope 
  launch { 
    delay(200L) 
    println("Task from runBlocking#2") 
  } 
  coroutineScope { // Creates a new coroutine scope 
    launch { 
      delay(500L) 
      println("Task from nested launch#3") 
    } 
    delay(100L) 
    println("Task from coroutine scope#1") // This line will be printed before nested launch 
  }
  println("Coroutine scope is over#4") // This line is not printed until nested launch completes 
}

output

Task from coroutine scope#1

Task from runBlocking#2

Task from nested launch#3

Coroutine scope is over#4