Kotlin - Coroutines

DevOks·2021년 7월 13일
1

kotlin

목록 보기
1/2
post-thumbnail

Summary : Coroutine + Flow 개념 정리 위한 문서

Coroutines

Concepts

  • 코틀린 동시성(비동기) 프로그래밍을 위한 API
  • 코틀린이 비동기 코드로 작업하는 방식은 코루틴을 사용하는 것인데, 코루틴은 일시 중단 가능한 연산, 즉 함수가 어느 시점에 실행을 일시 중단하고 나중에 재개할 수 있다는 개념이다.

Features


Coroutine의 장점

  • 멀티쓰레드의 병렬성과는 다르게 병행성의 특성을 갖고 있고,
  • 컨텍스트 스위칭 비용이 발생하지 않고 가볍게 동작 한다.
  • 특정 스레드상에서 동작 하게 지정 할 수 있다. 그리고 OS의 영향을 받지 않는다.
  • 개발자가 각 루틴을 언제 실행 할지, 언제 종료하는지 직접 지정이 가능하다.

API Guide


Kotlin

  1. Coroutine Builder (코루틴 생성)

    • 코틀린을 생성하는 3가지 방법
      1. launch (non-blocking)
      2. async (non-blocking)
      3. runBlocking (blocking)
    1. launch { ``` }

      • 결과 반환이 없는 빌더
      • job.join()을 사용해서 생성된 job이 종료될때까지 대기할 수도 있다.
      public fun CoroutineScope.launch(...):Job {...}
      
      val job = CoroutineScope.launch(Dispatchers.Default) {
      		sleep(2000)
      		dosomething() // do suspend function
      }
    2. async { ``` }

      • Deferred 객체를 반환하고, 그와 함께 결과(데이터)를 받을 수 있다.
      • deferred.await() 메소드를 사용하여 결과를 받는다.
      • Deferred Job을 상속받아 구현된 객체이다. (Java의 Future와 같은기능이다)
      
      public fun <T> CoroutineScope.async(...):Deferred<T> {...}
      
      val defeered = CoroutineScope.async(Dispatchers.Default) {
      		sleep(2000)
      		dosomething() // do suspend function
      }
      
      CoroutineScope.launch(Dispatchers.Main) {
          val dataOne = async(Dispatchers.IO) {
            fetchSomeData1()
          }
          val dataTwo = async(Dispatchers.Default) {
            fetchSomeData2()
          }
          showDialog(dataOne.await(), dataTwo.await())
      }
    3. runBlocking { ``` }

      • 새로운 코루틴을 실행하고 완료 될 때까지 현재 스레드를 차단한다.
      • Android UI 스레드에서 사용하는것을 되도록 피해야 한다. (ANR 발생 가능)
      public fun <T> runBlocking(...): T { ... }
      
      val result = runBlocking { sleep(2000)
      		dosometing() // do suspend function 
      }
    4. withContext { ``` }

      • 코루틴내부의 의 Context(디스패쳐)를 변경할 수 있다.
      • 안드로이드의 경우 io나 default 쓰레드에서 동작하는 코루틴이 처리 완료된 후, UI 스레드가 필요한 작업이 필요할 경우 바로 withContext(Dispatchers.Main) {} 로 감싸서 바로 스레드를 전환하여 수행 할 수 있다.
      public fun <T> withContext(...): T { ... }
      
      CoroutineScope.launch(Dispatchers.Main) {
      
      	val data = getDataFromServer()
      
      	withContext(Dispatcher.Main) {
      			showDialog(data) // do function UI Thread  
      	}
      }
  2. Coroutine Scope

    • 코루틴의 제어 범위, 실행범위를 지정할 수 있다.
    • 동일한 Scope안의 코루틴을 취소하는경우 모든 자식 코루틴들도 취소된다. 하지만, 다른 스코프를 지정하여 사용하는 코루틴은 취소되지 않는다.
    1. CoroutineScope

      • 특정한 스코포 및 디스패처를 지정할 수 있는 기본 코루틴 스코프
      • 안드로이드는 생명주기에 맞게 설계된 viewmodelScope, LifecycleScope등이 제공된다.
      public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
          ContextScope(if (context[Job] != null) context else context + Job())
      
      CoroutineScope(Dispatchers.Main).launch {
      	   dosometing() 
      }
    2. GlobalScope

      • 연결된 Job이 없기 때문에 예외를 발생해도 다른 코루틴에 영향을 미치지 않는다.
      • 싱글톤으로 최상위 레벨에서 코루틴을 시작한다.
      • Dispatchers.Default의 스레드를 기본으로 사용한다
      • 대부분 사용하는것을 추천하지 않는다.
      * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
      * and are not cancelled prematurely.
      
      public object GlobalScope : CoroutineScope{
          /**
           * Returns [EmptyCoroutineContext].
           */
          override val coroutineContext: CoroutineContext
              get() = EmptyCoroutineContext
      }
      
      GlobalScope(Dispatchers.Main).launch {
      	   dosometing() 
      }
  3. Coroutine Context and Dispatchers

    • 코루틴에 대한 종합적인 정보 집합체
    • 코루틴명을 지정하거나 , 디스패쳐를 지정하고, 예외를 처리할 수 있다.
    1. CoroutineNaming

    2. Dispatchers

      • 코루틴이 실행될 스레드를 지정하거나 스레드관련 처리를 한다.
      1. Dispatchers.Default : Cpu사용량이 많은 작업에 적합
      2. Dispatchers.IO : 네트워크,디스크작업에 최적화된 작업
      3. Dispatchers.Main : 안드로이드의 UI 스레드를 사용.
      4. Dispatchers.Unconfined : 쓰레드풀을 지정하지 않음. 특정용도로만 사용
      CoroutineScope(Dispatchers.Default).launch {
      	   dosometing() 
      }
    3. Job & Deferred

      • 코루틴 블록을 제어하고 값을 전달받기 위한 객체
      • launch, async에서 각각 반환되는 결과 객체
      • 주 메소드 : join() , cancel() , cancelAndJoin() , cancelChildren() , await() , awaitAll()
      val job = Coroutine(Dispatcher.IO).launch {
      		doSomething()
      }
      job.start()
      job.cancelAndJoin()
      CoroutineScope.launch(Dispatchers.Main) {
          val dataOne = async(Dispatchers.IO) {
            fetchSomeData1()
          }
          val dataTwo = async(Dispatchers.Default) {
            fetchSomeData2()
          }
          showDialog(dataOne.await(), dataTwo.await())
      }
    4. Coroutine Start Option

      • CoroutineStart.LAZY : job(deferred)의 start(), join() , await() 함수가 호출될때 까지 코루틴블록의 수행을 지연시킨다.
      • 이때 start() 함수를 사용하여 지연 실행 하는 경우와 join() 또는 await() 함수를 사용하여 지연 실행 하는 경우에는 해당 블록과 다른 코드들의 실행 순서에 차이가 발생함을 인지해야 한다.
      ** //숫자 코드 실행 순서 
      // 1
      CoroutineScope.launch(Dispatchers.IO) {
      	val deferred = async(start = CoroutineStart.LAZY) {
      	        delay(1000)
      					dosometing()// 3
      	}
      // 2
      	deferred.await()
      // 4
      }
      ** //숫자 코드 실행 순서 
      // 1
      CoroutineScope.launch(Dispatchers.IO) {
      	val deferred = async(start = CoroutineStart.LAZY) {
      	        delay(1000)
      					dosometing()// 4
      	}
      // 2
        deferred.start()
      // 3
      }
    5. ExceptionHandling

      • 코루틴은 취소가 아닌 다른 Exception이 발생하면 해당 코루틴의 부모의 코루틴까지 모두 다 취소시킨다.
      • 다른 자식 코루틴이나 부모코루틴이 종료 되지 않도록 하기 위해서 코루틴 블럭에 직접 CoroutineExceptionHandler를 설정하여 Exception을 내부적으로 처리 할 수 있다.
      • 블럭내에 context, throwable 객체를 인자값으로 받아서 처리 할 수 있다.
      public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler
      
      /**
       * CoroutineScope 내부 Exception 처리 Handler
       */
      private val coroutineExceptionHandler = CoroutineExceptionHandler { context , throwable ->
          Logger.w("$context ${throwable.message}")
          if(ConstApp.isDevMode())
              throwable.printStackTrace()
      }
  4. Cancellation

    • cancel() ,cancelAndJoin() 메소드로 코루틴 중단이 가능하다.
    • 코루틴 블럭 내부에서 finally 로 리소스를 닫는 작업등을 수 행 가능하다. finallly가 수행되고 중단 됨.
    • finally 안에서 suspend함수를 withContext(NonCancellable)로 감싸서 호출 가능하다.
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...") // 1
                delay(500L)
            }
        } finally {
    	        println("job: I'm running finally") // 3
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!") // 2
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.") // 4
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...") // 1
                delay(500L)
            }
        } finally {
            withContext(NonCancellable) {
                println("job: I'm running finally") //3
                doSomesuspendFunction() // 4
                println("job: And I've just delayed for 1 sec because I'm non-cancellable") // 5
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!") // 2
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.") // 6
  5. Timeouts

    • withTimeout() , withTimeoutOrNull() 메소드로 시간제한을 두고 Exception을 발생 시킬 수 있다.
    • withTimeoutOrNull 의 경우 수행 완료시 특정 결과값을 반환하고, 타임아웃발생시에는 Exception이 아닌 null값을 반환 한다.
    withTimeout(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    
    [결과] : 1300 ms 이후 TimeoutCancellationException 발생
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
        "Done" // will get cancelled before it produces this result
    }
    println("Result is $result")
    
    [결과] : 1300 ms 이후 "Result is null" 출력

Flow

아래 포스트에서 계속 흐름...
https://velog.io/@devoks/Kotlin-Flow

Documents


References


profile
[Android] Software Engineer

0개의 댓글