Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Naver Techcon - kotlin coroutines

TaeHwan
November 02, 2018

Naver Techcon - kotlin coroutines

Naver Presentation. Kotlin Coroutines for Android.

TaeHwan

November 02, 2018
Tweet

More Decks by TaeHwan

Other Decks in Education

Transcript

  1. Subroutine In computer programming, a subroutine is a sequence of

    program instructions that performs a specific task, packaged as a unit In different programming languages, a subroutine may be called a procedure, a function, a routine, a method, or a subprogram. The generic term callable unit is sometimes used. ୹୊ : https://en.wikipedia.org/wiki/Subroutine
  2. Subroutine fun test() fun MutableList<Int>.sum() mutableList().sum() return sum() subroutine੄ return੉

    ࠛ۞૑ӝ ੹ө૓
 test()੄ ׮਺ ۄੋਸ प೯ೞ૑ ঋח׮. +1 +2 . . .
  3. Subroutine example private fun MutableList<Int>.sum(): Int = this.sumBy { it

    } @Test fun test() { val sum = (0..10).toMutableList().sum() println(sum) } sum이 끝나야 return sum이 끝나야 println
  4. Coroutines According to Donald Knuth, Melvin Conway coined the term

    coroutine in 1958 when he applied it to construction of an assembly program. The first published explanation of the coroutine appeared later, in 1963 ୹୊ : https://en.wikipedia.org/wiki/Coroutine
  5. Coroutines Coroutines are computer-program components that generalize subroutines for non-preemptive

    multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations. Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes. ୹୊ : https://en.wikipedia.org/wiki/Coroutine
  6. Coroutines Entry point 여러 개 허용하는 subroutine 언제든 일시 정지하고

    다시 실행 가능 event loops, iterators, 무한 리스트, 파이프 같은 것을 구현하는데 적합
  7. Coroutines Start routine fun Int.countDown() Loop fun Int.countDown() Loop fun

    Int.countDown() Loop fun Int.countDown() Loop 10.countDown() . . . Entry point 여러 개 허용
  8. Coroutines example private suspend fun Int.countDown(currentIndex: Int) { for (index

    in this downTo 1) { // countdown from 10 to 1 tv_message.text = "Now index $currentIndex Countdown $index" // update text delay(200) } Log.i("TEMP", "Now index $currentIndex Done!") } Thread[main,5,main] Android UI Entry point 여러 개 허용 var currentIndex = 0 fab.onClick { CoroutineScope(Dispatchers.Main).launch { 10.countDown(++currentIndex) } }
  9. Android onClick - RxJava vs coroutines private val clickEventSubject =

    XXSubject.create<View>() private var currentIndex = 0 fab.setOnClickListener { clickEventSubject.onNext(it) } clickEventSubject .throttleFirst(500, TimeUnit.MICROSECONDS) .observeOn(Schedulers.io()) .map { currentIndex++ } .switchMap { Observable.zip(Observable.range(0, 10), Observable.interval(200, TimeUnit.MILLISECONDS), BiFunction<Int, Long, Int> { range, _ -> 10 - range }) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ index -> tv_message.text = "Now index $currentIndex Countdown $index" }, {}) RxJava Countdown 구현 var currentIndex = 0 fab.onClick { } private suspend fun Int.countDown(currentIndex: Int) { Log.i("TEMP", "Now index $currentIndex Done!") } CoroutineScope(Dispatchers.Default).launch { val job = launch(Dispatchers.Main) { 10.countDown(++currentIndex) } job.join() } for (index in this downTo 1) { } tv_message.text = "Now index $currentIndex Countdown $index" delay(200) Coroutines Countdown 구현 যוѱ ੍ӝ(੉೧ೞӝ) ؊ औաਃ? ೠߣ ੍যࠁભ
  10. Android onClick - RxJava vs coroutines private val clickEventSubject =

    XXSubject.create<View>() private var currentIndex = 0 fab.setOnClickListener { clickEventSubject.onNext(it) } clickEventSubject .throttleFirst(500, TimeUnit.MICROSECONDS) .observeOn(Schedulers.io()) .map { currentIndex++ } .switchMap { Observable.zip(Observable.range(0, 10), Observable.interval(200, TimeUnit.MILLISECONDS), BiFunction<Int, Long, Int> { range, _ -> 10 - range }) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ index -> tv_message.text = "Now index $currentIndex Countdown $index" }, {}) RxJava Countdown 구현 onClick 처리를 위한 Subject 생성 첫 번째만 처리하기 위한 throttleFirst 0..10까지 출력을 위한 ragne 200ms 처리를 위한 interval UI thread로 변경하고, 메시지 노출
  11. Android onClick - RxJava vs coroutines var currentIndex = 0

    fab.onClick { CoroutineScope(Dispatchers.Default).launch { val job = launch(Dispatchers.Main) { 10.countDown(++currentIndex) } job.join() } } private suspend fun Int.countDown(currentIndex: Int) { for (index in this downTo 1) { // countdown from 10 to 1 tv_message.text = "Now index $currentIndex Countdown $index" // update text delay(200) } Log.i("TEMP", "Now index $currentIndex Done!") } Count down 새로운 scope을 생성하고 default로 launch launch를 Main Thread로 변경 join()으로 UI thread 종료하기 전까지 대기 상위 scope thread에 따름 여기서는 UI
  12. Android onClick - RxJava vs coroutines private val clickEventSubject =

    PublishSubject.create<View>() private var currentIndex = 0 fab.setOnClickListener { clickEventSubject.onNext(it) } clickEventSubject .throttleFirst(500, TimeUnit.MICROSECONDS) .observeOn(Schedulers.io()) .map { currentIndex++ } .switchMap { Observable.zip(Observable.range(0, 10), Observable.interval(200, TimeUnit.MILLISECONDS), BiFunction<Int, Long, Int> { range, _ -> 10 - range }) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ index -> tv_message.text = "Now index $currentIndex Countdown $index" }, {}) RxJava vs coroutines var currentIndex = 0 fab.onClick { } private suspend fun Int.countDown(currentIndex: Int) { Log.i("TEMP", "Now index $currentIndex Done!") } CoroutineScope(Dispatchers.Default).launch { val job = launch(Dispatchers.Main) { 10.countDown(++currentIndex) } job.join() } for (index in this downTo 1) { } tv_message.text = "Now index $currentIndex Countdown $index" delay(200) Coroutines Countdown 구현
  13. Android onClick - RxJava vs coroutines Observable과 Streams 기존 Thread

    보다 간단한 코드로 처리 stream을 통해 데이터 처리 용이 Thread간 교체가 간단 RxJava를 활용한 수 많은 라이브러리 활용 가능 러닝커브가 높음 용어를 모르면 코드 활용 이유를 알 수 없음 RxJava Kotlin coroutines 함수 형태라 읽기 쉽다 light-weight threads 모든 routine 동작을 개발자가 처리 가능 아직은 필요한 라이브러리를 구현해서 사용 해야 함 RxView와 같은 라이브러리 개발 필요
  14. Android onClick - RxJava vs coroutines 처음 학습 비용이 높다

    수 많은 라이브러리 활용 가능 예제도 많고, 문서도 잘 되어있다. RxJava Kotlin coroutines 처음 학습 비용이 낮다 아직은 부족한 라이브러리 직접 만들 수 있고, 문서도 잘 되어있다
  15. Kotlin coroutines Kotlin 다양한 platform 제공 Server-side Desktop Mobile Application

    다양한 언어에서 제공하던 주요 라이브러리 제공 C#, ECMAScript : async/await Go : channels, select C#, Python : generators/yield ؊ ੗ࣁೠ ղਊ਷ https://kotlinlang.org/docs/reference/coroutines-overview.html Coroutine light-weight threads
  16. Kotlin coroutines @Test fun test() { GlobalScope.launch { // launch

    new coroutine in background and continue delay(300L) // 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(500L) // block main thread for 2 seconds to keep JVM alive } blocking과 non-blocking Main Thread에서 Thread.sleep(500L)을 부르면 0.5초동안 앱이 멈추는 현상 발생 후 넘어간다.
  17. Kotlin coroutines Thread.sleep() : Thread 처리 전까지 Main Thread가 멈춘다.

    delay : 별도의 coroutine에서 동작하여 멈추지 않고 넘어간다. blocking과 non-blocking
  18. Kotlin coroutines @Test fun test() CoroutineScope(Dispatchers.Unconfined).launch { delay(300L) println("World!") }

    println("Hello,") } runBlocking Result Subroutine Coroutine delay(500) runBlocking { } = {
  19. Kotlin coroutines runBlocking은 함수의 처리가 끝날때까지 대기 delay()를 걸어두면 delay

    시간만큼 대기하고 return Android에서 runBlocking을 UI에서 잘못 사용하면 멈추는 현상 발생 runBlocking delay를 이용해 non-blocking을 할 수 있음 runBlocking, CoroutineScope, GlobalScope 안에서 동작해야 함 delay
  20. Kotlin coroutines 가장 기본적인 Scope Thread 형태를 지정(Main, Default, IO

    등을 지정) CoroutineScope(Main, Default, IO …). launch, async 등을 통해 scope을 실행 CoroutineScope
  21. Kotlin coroutines Activity/Fragment Lifecycle에 따라야 한다 onDestroy() : cancel 하도록

    코드 추가 CoroutineScope(/* thread type */).launch {}로 실행 launch {}의 return job의 동작을 지정 가능 join() : scope 동작이 끝날때까지 대기하며, suspend에서 호출 가능 cancel() : 동작을 종료하도록 호출 start() : scope이 아직 시작하지 않을 경우 start, scope의 상태를 확인 CoroutineScope 중요한 점
  22. Kotlin coroutines @Suppress("FunctionName") public fun CoroutineScope(context: CoroutineContext): CoroutineScope = ContextScope(if

    (context[Job] != null) context else context + Job()) CoroutineScope의 interface 정의 public interface CoroutineScope { @Deprecated(level = DeprecationLevel.HIDDEN, message = "Deprecated in favor of top-level extension property") public val isActive: Boolean get() = coroutineContext[Job]?.isActive ?: true /** * Context of this scope. */ public val coroutineContext: CoroutineContext }
  23. Kotlin coroutines @Test fun test() = runBlocking { delay(300L) println("World!")

    } println("Hello,") } CoroutineScope 활용 - Delay 대신 job.join() delay(500L) CoroutineScope(Dispatchers.Unconfined).launch { job.join() val job =
  24. Kotlin coroutines @Test fun test() = runBlocking { val job

    = CoroutineScope(Dispatchers.Unconfined).launch { delay(300L) println("World!") } println("Hello,") job.join() } CoroutineScope 활용 새로운 scope을 생성하고 default로 launch launch에서 CoroutineScope에서 지정한 default Thread로 사용 join()으로 default thread 종료하기 전까지 대기
  25. Kotlin coroutines CoroutineScope 상속 받아 구현 Demon, Application 등에서 사용

    Application의 lifetime에 따라 동작하는 scope에서 사용 추천 GlobalScope는 Dispatchers.Unconfined(worker thread)에서 동작 GlobalScope.launch(/* thread type */) {}로 실행 GlobalScope
  26. Kotlin coroutines object GlobalScope : CoroutineScope { /** * @suppress

    **Deprecated**: Deprecated in favor of top-level extension property */ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Deprecated in favor of top-level extension property") override val isActive: Boolean get() = true /** * Returns [EmptyCoroutineContext]. */ override val coroutineContext: CoroutineContext get() = EmptyCoroutineContext } GlobalScope API 코드
  27. Kotlin coroutines fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = for (number in this@sqrt)

    { send(Math.sqrt(number.toDouble())) } GlobalScope 사용 예 GlobalScope.produce(Dispatchers.Unconfined) { }
  28. Kotlin coroutines suspend를 활용하여 함수 분할 할 수 있다 suspend로

    만들어진 함수는 사용하기 전까지는 동작하지 않는다. Suspend 키워드를 사용하는 함수는 CoroutineScope에서 만 사용할 수 있다. suspend
  29. Kotlin coroutines suspend fun CoroutineScope.loadData( body: suspend CoroutineScope.(item: String) ->

    Unit) { val item = "" body(item) } loadData { item -> // Coroutine scope ੿੄ } suspend suspend CoroutineScope(Dispatchers.Main).launch { } delay(100L)
  30. Job

  31. Kotlin coroutines CoroutineScope의 return에는 job 객체를 넘겨준다. 이 job을 통해

    routine의 취소, 실행, 종료를 대기할 수 있다 job.cancel() : 종료하도록 유도한다 job.join() : 종료를 대기하며 호출은 suspend에서 가능하다 job.start() : coroutine의 시작을 확인할 수 있으며, 시작 상태라면 true Job
  32. Kotlin coroutines var currentIndex = 0 fab.onClick { CoroutineScope(Dispatchers.Default).launch {

    val job = launch(Dispatchers.Main) { 10.countDown(++currentIndex) } job.join() } } private suspend fun Int.countDown(currentIndex: Int) { for (index in this downTo 1) { // countdown from 10 to 1 tv_message.text = "Now index $currentIndex Countdown $index" // update text delay(200) } Log.i("TEMP", "Now index $currentIndex Done!") } Count down 새로운 scope을 생성하고 default로 launch launch를 Main Thread로 변경 join()으로 UI thread 종료하기 전까지 대기 상위 scope thread에 따름 여기서는 UI
  33. Android onClick 잘 활용하기 no. 1 Out 0 .. no.

    2 Out 0 .. no. 4 Out 5 .. no. 5 Out 5 End no. 1 Done! .. no. 9 Out 4 .. End no. 2 Done! .. no. 6 Out 5 End no. 3 Done! .. End no. 4 Done! .. no. 8 Out 5 .. End no. 5 Done! no. 9 Out 5 .. End no. 6 Done! .. no. 10 Out 5 .. End no. 7 Done! no. 11 Out 5 .. End no. 8 Done! .. no. 12 Out 5 .. End no. 9 Done!
  34. Android onClick 잘 활용하기 첫 번째 이벤트 만 허용하기 위해서

    throttleFirst 활용 시간으로 first의 시간을 지정하여 아래와 같은 문제 발생외처리 필요 RxJava 버튼 처리
  35. Android onClick 잘 활용하기 첫 번째 이벤트 만 허용하기 위해서

    throttleFirst 활용 시간으로 first의 시간을 지정하여 아래와 같은 문제 발생외처리 필요 RxJava 버튼 처리 ޙઁ੼ 500L ੉റী ׮਺ ੉߮౟о ߊࢤೠ׮.
  36. Android onClick 잘 활용하기 첫 번째 이벤트 만 허용하기 위해서

    버튼의 상태를 변경해서 처리한다? 다행히도 coroutine에서는 그럴 필요 없다. Coroutine으로 처리
  37. Android onClick 잘 활용하기 private fun View.onClick(action: suspend (View) ->

    Unit) { val event = GlobalScope.actor<View>(Dispatchers.Main) { for (event in channel) action(event) } setOnClickListener { event.offer(it) } } var currentIndex = 0 fab.onClick { 10.countDown(currentIndex++) } Coroutine GlobalScope.actor<T> 활용하기 1. Singletone의 GlobalScope 활용 2. actor 이용 event 받기 3. actor에 offer로 event 보내기 4. 받은 event를 Higher-Order function으로 넘겨서 정의하도록 한다. 6. 람다 표현으로 countDown 구현 5. 이때 Higher-Order function 정의는 suspend가 포함되어야 한다
  38. UnitTest/Main Thread에서 활용하기 쉽게 Dispatchers 하나로 활용 UnitTest에서 Main thread를

    활용할 수 없기에 기본으로 처리 할 수 있도록 작성 CoroutineScope을 Base에 작성하여 release를 쉽게 하도록 처리 onClick에 coroutine 활용을 위한 GlobalScope 적용 안드로이드에서 코루틴 활용 Android Coroutines
  39. Android Coroutines sealed class DispatchersProviderSealed { open val main: CoroutineContext

    by lazy { Dispatchers.Main } open val default: CoroutineContext by lazy { Dispatchers.Default } } /** * ӝఋ Threadܳ ਤೠ Dispatchers ੿੄ */ object DispatchersProvider : DispatchersProviderSealed() /** * Unit Testܳ ਤೠ Dispatchers ੿੄ */ object TestDispatchersProvider : DispatchersProviderSealed() { override val main: CoroutineContext = Dispatchers.Unconfined override val default: CoroutineContext = Dispatchers.Unconfined } Dispatchers 정의(UnitTest/Default Thread)
  40. Android Coroutines abstract class CoroutineScopeActivity : AppCompatActivity(), CoroutineScope { private

    val job: Job = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onDestroy() { super.onDestroy() job.cancel() } } CoroutineScope을 상속받아 구현 onDestroy()에서 job을 종료하도록 한다. Job을 미리 생성하여 CoroutineContext에 미리 지정 할 수 있다. Activity에서 사용할 기본 Context를 정의한다.
  41. CoroutineScope을 상속받아 구현하면 기본 CoroutineScope으로 정의되어있다 launch, actor<E>을 사용하면 코드

    간결 및 자동으로 종료 처리 해준다. Default 상위 Activity에서 정의한 CoroutineScope의 Thread를 활용한다 필요시 launch, actor에서 Thread를 언제든 변경 가능하다 다양한 CoroutineScope 구현 ViewModel LifecycleObservable Fragment CoroutineScope을 상속받아 구현 Android Coroutines
  42. Android Coroutines abstract class CoroutineScopeActivity : CoroutineScopeActivity() { launch {

    // UI Threadীࢲ ୊ܻ } launch(Dispatchers.Default) { // Default Threadীࢲ ୊ܻ } actor<Generic Type> { // UI Threadীࢲ event ୊ܻ for (event in channel) action(event) } actor<Generic Type>(Dispatchers.Default) { // Default Theadীࢲ ୊ܻ for (event in channel) action(event) } } CoroutineScope 상속 받은 Activity에서 routine 사용하기 launch, actor에서는 언제든지 Thread type을 변경할 수 있다.
  43. Higher-Order function + kotlin Extensions을 활용 GlobalScope을 활용하거나, CoroutineScope을 활용

    할 수 있다 동작 범위에 따라서 GlobalScope, CoroutineScope을 선택함이 좋다 onClick 처리 Android Coroutines
  44. Android Coroutines class CoroutinesSendChannelOnClickEvent<E>( private val view: View, private val

    bgBody: suspend (item: View) -> E, private val dispatcherProvider: DispatchersProviderSealed = DispatchersProvider, private val job: Job? = null) { fun consumeEach(uiBody: (item: E) -> Unit): CoroutinesSendChannelOnClickEvent<E> { val clickActor = CoroutineScope(dispatcherProvider.main + (job ?: EmptyCoroutineContext)).actor<View> { this.channel.map(context = dispatcherProvider.default, transform = bgBody).consumeEach(uiBody) } view.setOnClickListener { clickActor.offer(it) } return this } } fun <E> View.onClick(dispatcherProvider: DispatchersProviderSealed = DispatchersProvider, job: Job? = null, bgBody: suspend (item: View) -> E): CoroutinesSendChannelOnClickEvent<E> = CoroutinesSendChannelOnClickEvent(this, bgBody, dispatcherProvider, job) infix fun <E> CoroutinesSendChannelOnClickEvent<E>.consume(uiBody: (item: E) -> Unit) { this.consumeEach(uiBody) } onClick 만들기, background에서 처리하고, UI에 던져주기 View : click을 위한 View background : Higher-Order Function Provider : 지정 Job : 종료 처리를 위한 job 추가 Offer 처리를 위한 CoroutineScope 생성 생성을 간단하게 하기 위한 function 2개
  45. Android Coroutines fab.onClick(job = job) { loadNetwork() } consume {

    tv_message.text = it } private suspend fun loadNetwork(): String { delay(300) return "currentIndex ${currentIndex++}" } onClick 만들기, background에서 처리하고, UI에 던져주기 click을 처리하고, background에서 loadNetwork() Temp load network
  46. 이미 JakeWharton 배포. Dependency 추가 implementation ‘com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' Deferred<E>를 활용하여 return을

    처리할 수 있다. Retrofit2 kotlin coroutines adapter Android Coroutines interface RetrofitService { @GET("/posts") fun getPosts(): Deferred<Response<List<Post>>> }
  47. Android Coroutines object RetrofitFactory { const val BASE_URL = “https://url"

    fun makeRetrofitService(): RetrofitService { return Retrofit.Builder() .baseUrl(BASE_URL) .addCallAdapterFactory(CoroutineCallAdapterFactory() ) .build().create(RetrofitService::class.java) } } Retrofit2 kotlin coroutines adapter
  48. Android Coroutines launch { val request = service.getPosts() val response

    = request.await() if (response.isSuccessful) { // Pass in the response.body() to your adapter } else { toast("Error ${response.code()}") } } Retrofit2 kotlin coroutines adapter