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

Kotlin coroutines

TaeHwan
November 22, 2018

Kotlin coroutines

Jetbrains day in seoul 2018

TaeHwan

November 22, 2018
Tweet

More Decks by TaeHwan

Other Decks in Programming

Transcript

  1. ࣗѐ • GDG Seoul ਍৔૓ • ٘۽੉٘ ա੉எ ਍৔૓ •

    RGP Korea • ਃӝਃ উ٘۽੉٘ ѐߊ • Blog : Է ݆਷ ѐߊ੗о غ੗! (https://thdev.tech)
  2. ਋ܻо ঌҊ੓ח ೣࣻ • ഐ୹ೠ Ҕਵ۽ جইয়ӝ ਤ೧ࢲח return੉ ೙ਃೞ׮

    • return ੉੹ীח ݫੋ ೣࣻ੄ ׮਺ ۄੋਸ प೯ೞ૑ ঋח׮ • ੉۠ ੌ߈੸ੋ ೣࣻܳ Subroutine੉ۄ ೠ׮.
  3. 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
  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 fun calc() fun MutableList<Int>.sum() Loop fun MutableList<Int>.sum() Loop fun

    MutableList<Int>.sum() Loop fun MutableList<Int>.sum() Loop (0..10).toMutableList().sum() . . .
  7. Coroutines Thread? One can think of a coroutine as a

    light-weight thread. The biggest difference is that coroutines are very cheap, almost free: we can create thousands of them, and pay very little in terms of performance. Light-weight thread.
  8. Coroutines private suspend fun MutableList<Int>.sum(): Int = this.sumBy { it

    } @Test fun test() { println("run") CoroutineScope(Dispatchers.Default).launch { (0..10).forEach { val sum = (it..10).toMutableList().sum() println(sum) } } println("wait") runBlocking { delay(500L) } println("Test end") } प೯ Ѿҗ run wait 55 55 54 . . . 27 19 10 Test end
  9. Coroutines private suspend fun MutableList<Int>.sum(): Int = this.sumBy { it

    } @Test fun test() { println("run") CoroutineScope(Dispatchers.Default).launch { (0..10).forEach { val sum = (it..10).toMutableList().sum() println(sum) } } println("wait") runBlocking { delay(500L) } println("Test end") } 1. 500 ms ؀ӝ 3. 500 ms ؀ӝ റ Test end 2. 500 ms زউ 0..10ਸ ؊ೞח sum प೯
  10. Kotlin Coroutines Kotlin 1.3.10/Coroutines 1.0.1 ੉ਊ ௏ܖ౯ ഝਊ оמ Asynchronous

    or non-blocking programming ઁҕ ׮নೠ platform ઁҕ Server-side Desktop Mobile Application
  11. Kotlin Coroutines dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1' } buildscript { ext.kotlin_version

    = ‘1.3.10' } repository { jcenter() } Android implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
  12. Kotlin Coroutines private suspend fun MutableList<Int>.sum(): Int = this.sumBy {

    it } @Test fun test() { println("run") CoroutineScope(Dispatchers.Default).launch { (0..10).forEach { val sum = (it..10).toMutableList().sum() println(sum) } } println("wait") runBlocking { delay(500L) } println("Test end") } CoroutineScope runBlocking suspend Dispatchers Launch Delay
  13. CoroutineScope public interface CoroutineScope { /** * Context of this

    scope. */ public val coroutineContext: CoroutineContext } internal class ContextScope(context: CoroutineContext) : CoroutineScope { override val coroutineContext: CoroutineContext = context } @Suppress("FunctionName") public fun CoroutineScope(context: CoroutineContext): CoroutineScope = ContextScope(if (context[Job] != null) context else context + Job())
  14. CoroutineScope ௏ܖ౯ ੿੄ܳ ਤೠ Scope ઁҕ CoroutineContext ഋకܳ ૑੿ Main,

    Default, IO … launch, async ١ਸ ా೧ scope प೯ ഋక ੿੄
  15. CoroutineScope Activity Lifecycle class SampleActivity : AppCompatActivity(), CoroutineScope { private

    val job = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onDestroy() { super.onDestroy() job.cancel() } } ز੘ ઺ੋ CoroutineScope੄ Jobਸ ઺૑ೞب۾ ਃ୒ೠ׮ ӝࠄ Threadܳ Mainਵ۽ ୊ܻೞب۾ ੿੄ೠ׮
  16. CoroutineScope Job CoroutineScope(/* thread type */)ਵ۽ ੿੄ೞݴ, launch, actor ١

    ഝਊ launch {}੄ return jobਸ ా೧ ز੘ ૑੿ оמ join() : scope ز੘੉ ՘զ ٸө૑ ؀ӝೞݴ, CoroutinScope উীࢲ ഐ୹ оמ cancel() : ੘স ઺ੋ ز੘ਸ ઙܐ ਬب start() : Scope ࢚కܳ ഛੋೞݴ, ই૒ द੘ೞ૑ ঋওਸ ҃਋ start
  17. CoroutineScope Scope ઙܐܳ delay ؀ӝೞח ҃਋ private suspend fun MutableList<Int>.sum():

    Int { val ret = this.sumBy { it } delay(100) return ret } @Test fun test() { println("run") CoroutineScope(Dispatchers.Default).launch { (0..10).forEach { val sum = (it..10).toMutableList().sum() println(sum) } } println("wait") runBlocking { delay(500L) } println("Test end") } दрਵ۽ח ੿ഛೞ૑ ঋ׮ ੐੄۽ delay ੸ਊ run wait 55 55 54 52 Test end
  18. CoroutineScope Scope ઙܐܳ delay ؀ӝೞח ҃਋ private suspend fun MutableList<Int>.sum():

    Int { val ret = this.sumBy { it } delay(100) return ret } @Test fun test() { println("run") CoroutineScope(Dispatchers.Default).launch { (0..10).forEach { val sum = (it..10).toMutableList().sum() println(sum) } } println("wait") runBlocking { delay(500L) } println("Test end") } दрਵ۽ח ੿ഛೞ૑ ঋ׮ ੐੄۽ delay ੸ਊ run wait 55 55 54 52 Test end Ӓۢ delay ؀न ੘স੉ ՘աӡ ӝ׮۰ࠁ੗.
  19. CoroutineScope Scope ઙܐܳ delay ؀ӝೞח ҃਋ private suspend fun MutableList<Int>.sum():

    Int { val ret = this.sumBy { it } delay(100) return ret } @Test fun test() { println("run") (0..10).forEach { val sum = (it..10).toMutableList().sum() println(sum) } } println("wait") runBlocking { } println("Test end") } run wait 55 55 54 52 Test end CoroutineScope(Dispatchers.Default).launch { delay(500L) job.join() val job =
  20. CoroutineScope Scope ઙܐܳ delay ؀ӝೞח ҃਋ private suspend fun MutableList<Int>.sum():

    Int { val ret = this.sumBy { it } delay(100) return ret } @Test fun test() { println("run") val job = CoroutineScope(Dispatchers.Default).launch { (0..10).forEach { val sum = (it..10).toMutableList().sum() println(sum) } } println("wait") runBlocking { job.join() } println("Test end") } launch੄ returnੋ Jobਸ ഝਊೞݶ ৘ஏ ࠛоמೠ दрਵ۽ ઙܐܳ ӝ׮ܾ ೙ਃо হয૓׮.
  21. GlobalScope object GlobalScope : CoroutineScope { /** * Returns [EmptyCoroutineContext].

    */ override val coroutineContext: CoroutineContext get() = EmptyCoroutineContext }
  22. GlobalScope CoroutineScopeਸ ࢚ࣘ ߉ই ҳഅ೧ك object ҳഅ୓ CoroutineScopeҗ ׮ܰѱ Application/Demon

    Lifecycleਸ ٮܰب۾ ѐߊ ೙ਃ GlobalScopeਵ۽ द੘ೞݴ launch(/* thread type */), actor ١ਸ ഝਊ
  23. Coroutines ز੘ launch CoroutineScope/GlobalScope੄ ௏٘ܳ ੿੄ XxScope.launch(/* Thread type ૑੿

    */) ੉޷ Scope উ੉ۄݶ launch(/* Thread type ૑੿*/)ਵ۽ thread type ߸҃ Thread type(Dispatchers) ૑੿ೞ૑ ঋਸ ҃਋ ࢚ਤ scope thread typeਸ ٮܲ׮
  24. Coroutines ز੘ async/await async : ௏٘ ࠶ۅਸ ੿੄ await :

    ௏٘ ࠶ۅ੉ ઙܐೞӡ ӝ׮ܽ׮ async(/* Thread type ૑੿ */) launch৬ زੌೞݴ, ૑੿ೞ૑ ঋਵݶ ࢚ਤ scope thread typeਸ ٮܲ׮ 1ѐ ੉࢚੄ coroutine زӝചо ೙ਃೠ ҃਋ ਬਊೞѱ ࢎਊೡ ࣻ ੓׮
  25. Coroutines ز੘ Actor actorী ݫद૑ܳ ੹࣠ೞҊ, ੉ܳ ୊ܻೡ ࣻ ੓׮.

    actor<T> {} ഋక۽ ؘ੉ఠܳ ࠁմ׮ send(coroutine scope) offer(no coroutine scope)
  26. Coroutines ز੘ Channel val channel = Channel<Int>() launch { //

    this might be heavy CPU-consuming computation or async logic, we'll just send five squares for (x in 1..5) channel.send(x + x) } // here we print five received integers: repeat(5) { println(channel.receive()) } println("Done!") 2 4 6 8 10 Done!
  27. Coroutines ز੘ produce produceܳ ా೧ ReceiveChannelী ؘ੉ఠܳ send ೡ ࣻ

    ੓׮ ੌ੿दр, ੌ੿ ੉߮౟ܳ ReceiveChannel۽ ੹࣠ೡ ࣻ ੓׮
  28. Coroutines ز੘ produce private suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>)

    { select<Unit> { // <Unit> means that this select expression does not produce any result fizz.onReceive { value -> // this is the first select clause println("fizz -> '$value'") } buzz.onReceive { value -> // this is the second select clause println("buzz -> '$value'") } } } fun CoroutineScope.fizz() = produce<String> { while (true) { // sends "Fizz" every 300 ms delay(300) send("Fizz") } } fun CoroutineScope.buzz() = produce<String> { while (true) { // sends "Buzz!" every 500 ms delay(500) send("Buzz!") } } val fizz = fizz() val buzz = buzz() repeat(7) { selectFizzBuzz(fizz, buzz) } coroutineContext.cancelChildren() // cancel fizz & buzz coroutines fizz -> 'Fizz' buzz -> 'Buzz!' fizz -> 'Fizz' fizz -> 'Fizz' buzz -> 'Buzz!' fizz -> 'Fizz' buzz -> ‘Buzz!'
  29. Dispatchers launch { // context of the parent, main runBlocking

    coroutine println("main runBlocking : I'm working in thread $ {Thread.currentThread().name}") } launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher println("Default : I'm working in thread $ {Thread.currentThread().name}") } launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread println("newSingleThreadContext: I'm working in thread $ {Thread.currentThread().name}") } ૑੿ೞ૑ ঋওӝী ৻ࠗ currentThreadী ٮܲ׮. Work threadীࢲ ز੘ Work thread۽ ࢜۽਍ MyOwnThreadܳ ࢤࢿ
  30. suspend suspend ఃਕ٘ܳ ୶оೞৈ ೣࣻ ࠙ೡ suspend ੿੄ೠ ೣࣻח ࢎਊೞӝ

    ੹ө૑ ز੘ೞ૑ ঋח׮ suspend ೣࣻח CoroutineScope ղীࢲ ݅ ࢎਊೡ ࣻ ੓׮
  31. suspend private suspend fun waitOne(): Int { delay(100L) return 100

    } private suspend fun waitTwo(): Int { delay(200L) return 200 } @Test fun testTwo() = runBlocking { val one = CoroutineScope(Dispatchers.Default).async { waitOne() } val two = CoroutineScope(Dispatchers.Default).async { waitTwo() } println("wait ${one.await()} ${two.await()}") } 100 ms delay റ 100ਸ ܻఢ 200 ms delay റ 200ਸ ܻఢ ਤ 2ѐ੄ Ѿҗܳ async/awaitਸ ੉ਊೞৈ Ѿҗܳ ୹۱
  32. Timer ҳഅ അ੤ दр ޷ې दр ޷ې दр - അ੤

    दрਵ۽ അ੤दрਸ ҳೞӝ 1ୡী ೠߣঀ ഐ୹ System.currentTimeMillis() System.currentTimeMillis() + N࠙ റ CoroutineScope উীࢲ ୊ܻ CoroutineScope੄ delay() ഝਊ UI৬ Background ܻ࠙ܳ ਤೠ Dispatchers ੿੄
  33. Timer ҳഅ private val simpleDateFormat: SimpleDateFormat = SimpleDateFormat("mm:ss", Locale.getDefault()) private

    suspend fun timer(timerMillis: Long, timerStartTime: Long) = CoroutineScope(Dispatchers.Default).launch { while (isActive) { val nowRemainingMills: Long = System.currentTimeMillis() - timerStartTime val timeRemainingMills: Long = timerMillis - nowRemainingMills if (timeRemainingMills >= 0) { println("now ${simpleDateFormat.format(timeRemainingMills)}") } else { break } delay(1000L) } } @Test fun testTimer() = runBlocking { val endTime = System.currentTimeMillis() + 10000 val job = timer(endTime - System.currentTimeMillis(), System.currentTimeMillis()) job.join() } 1ୡী ೠߣঀ ഐ୹ೞب۾ ؀ӝ Timer ઙܐೡٸө૑ ؀ӝ
  34. RxJava৬ coroutines 처음 학습 비용이 높다 수 많은 라이브러리 활용

    가능 예제도 많고, 문서도 잘 되어있다. 처음 학습 비용이 낮다 아직은 부족한 라이브러리 직접 만들 수 있고, 문서도 잘 되어있다
  35. 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৬ coroutines var currentIndex = 0 fab.setOnClickListener { 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!") } private val clickEventSubject = XXSubject.create<View>() CoroutineScope Dispatchers.Default launch Observable.interval(200, TimeUnit.MILLISECONDS) Observable.range(0, 10) observeOn(AndroidSchedulers.mainThread()) tv_message.text = "Now index $currentIndex Countdown $index" for (index in this downTo 1) delay(200) .observeOn(Schedulers.io())
  36. RxJava৬ coroutines RxJava Coroutines ੿੄ Flowable, Observable, Single ١ CoroutineScope,

    GlobalScope ੹୓ झாે۞ ૑੿ subscribeOn(schedulers.io()) ࢤࢿ੗ীࢲ ૑੿ झாે۞ ߸҃ observeOn(schedulers.io()) launch, actor ١ ੿੄ೡٸ ૑੿ झாે۞ ߧਤ observeOnীࢲ ੿੄ೠ ׮਺ࠗఠ ݽف ੸ਊ {} উীࢲ݅ ੸ਊ द੘ subscribe ੿੄о ੓যঠ ز੘ ૊द प೯
  37. 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() } } Android Coroutines onDestroy()에서 job을 종료하도록 한다. Job을 미리 생성하여 CoroutineContext에 미리 지정 할 수 있다. Activity에서 사용할 기본 Context를 정의한다.
  38. 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) } } launch, actor 등에서는 언제든지 Thread type을 변경할 수 있다.
  39. ਋ܻо ਗೞח दաܻয় 단 1번만 실행해야 한다는 가정 사용자가 버튼을

    누른다 애니메이션을 실행(Progress)/버튼 비활성화 완료하면 애니메이션 종료/버튼 활성화 문제 1. 버튼 비활성화 네트워크 시도 중 오류 발생에 따른 버튼 활성화가 쉽지 않음 문제 2. RxJava 활용으로 버튼 비활성화 대신 시간을 지정 일정 시간마다 버튼이 눌릴 수 있어 결국 중복 이벤트 발생 문제 3. 원하는 이벤트 종료 때까지 애니메이션 처리 사용자 거부감
  40. Coroutineਵ۽ ೧Ѿ೧ࠁ੗ 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++) } 1. Singletone의 GlobalScope 활용 2. actor 이용 event 받기 3. actor에 offer로 event 보내기 4. 받은 event를 Higher-Order function으로 넘겨서 정의하도록 한다. 6. 람다 표현으로 countDown 구현 5. 이때 Higher-Order function 정의는 suspend가 포함되어야 한다
  41. Androidীࢲ ਬਊೠ onClick ୊ܻೞӝ Higher-Order function + kotlin Extensions을 활용

    GlobalScope을 활용하거나, CoroutineScope을 활용 할 수 있다 동작 범위에 따라서 GlobalScope, CoroutineScope을 선택함이 좋다
  42. open class CoroutineUIEvent<E, R>(private val bgBody: suspend (item: E) ->

    R, private val dispatcherProvider: DispatchersProviderSealed = DispatchersProvider, private val job: Job? = null) { private lateinit var uiSender: SendChannel<E> @ObsoleteCoroutinesApi open fun initUiSender(uiBody: (item: R) -> Unit): CoroutineUIEvent<E, R> { uiSender = CoroutineScope(dispatcherProvider.main + (job ?: EmptyCoroutineContext)).actor { this.channel.map(context = dispatcherProvider.default, transform = bgBody).consumeEach(uiBody) } return this } open fun offer(element: E) { if (::uiSender.isInitialized) { uiSender.offer(element) } } } onClickਸ ୊ܻೞӝਤೠ SendChannelਸ ୶о UiSender ୡӝചܳ ਤೠ ௏٘ CoroutineScopeਵ۽ ୡӝച offer۽ ׮਺ ੉߮౟ܳ ੹׳ೠ׮ Androidীࢲ ਬਊೠ onClick ୊ܻೞӝ
  43. /** * Button onClick and background body. * @param dispatcherProvider

    : default Default thread and main thread * @param job : default null, add job * @param bgBody : background thread */ fun <E, R> createUiEvent(dispatcherProvider: DispatchersProviderSealed = DispatchersProvider, job: Job? = null, bgBody: suspend (item: E) -> R): CoroutineUIEvent<E, R> = CoroutineUIEvent(bgBody, dispatcherProvider, job) /** * run on ui thread. * @param uiBody run ui thread body */ @ObsoleteCoroutinesApi infix fun <E, R> CoroutineUIEvent<E, R>.runUi(uiBody: (item: R) -> Unit): CoroutineUIEvent<E, R> = this.initUiSender(uiBody) UI Eventܳ औѱ ٜ݅ӝ ਤೠ ௏٘੉ݴ, BG ॳۨ٘ ୊ܻ UI ॳۨ٘ীࢲ ࢎਊೡ ௏٘ ࠶ۅਸ ୡӝച Androidীࢲ ਬਊೠ onClick ୊ܻೞӝ
  44. fab.onClick { gitHubService.contributors(tv_owner.text.toString(), tv_repo.text.toString()) .onErrorReturn { mutableListOf(Contributor("", 0)) } .doOnError

    { tv_message.text = "Search error ${it.message}" } .await() }.runUi { for ((name, contributions) in it) { tv_message.text = "$name as $contributions contributions!" } } BG Theadীࢲ ؘ੉ఠܳ ࠛ۞ৡ׮ UI Threadীࢲ View јन Androidীࢲ ਬਊೠ onClick ୊ܻೞӝ
  45. Android RxJava৬ ೣԋ ࢎਊೞӝ https://github.com/Kotlin/kotlinx.coroutines/blob/master/reactive/ README.md kotlinx-coroutines-reactive -- utilities for

    Reactive Streams kotlinx-coroutines-reactor -- utilities for Reactor kotlinx-coroutines-rx2 -- utilities for RxJava 2.x
  46. interface GitHub { @GET("/repos/{owner}/{repo}/contributors") fun contributors( @Path("owner") owner: String, @Path("repo")

    repo: String ): Single<List<Contributor>> @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Single<List<Repo>> } val github = retrofit.create(GitHub::class.java) val contributors = github.contributors("JetBrains", "Kotlin") .await().take(10) RxJava੄ ਽׹ਸ ӝ׮ܻҊ, coroutinesਵ۽ return Android RxJava৬ ೣԋ ࢎਊೞӝ