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
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
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
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) } }
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
보다 간단한 코드로 처리 stream을 통해 데이터 처리 용이 Thread간 교체가 간단 RxJava를 활용한 수 많은 라이브러리 활용 가능 러닝커브가 높음 용어를 모르면 코드 활용 이유를 알 수 없음 RxJava Kotlin coroutines 함수 형태라 읽기 쉽다 light-weight threads 모든 routine 동작을 개발자가 처리 가능 아직은 필요한 라이브러리를 구현해서 사용 해야 함 RxView와 같은 라이브러리 개발 필요
다양한 언어에서 제공하던 주요 라이브러리 제공 C#, ECMAScript : async/await Go : channels, select C#, Python : generators/yield ؊ ࣁೠ ղਊ https://kotlinlang.org/docs/reference/coroutines-overview.html Coroutine light-weight threads
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초동안 앱이 멈추는 현상 발생 후 넘어간다.
시간만큼 대기하고 return Android에서 runBlocking을 UI에서 잘못 사용하면 멈추는 현상 발생 runBlocking delay를 이용해 non-blocking을 할 수 있음 runBlocking, CoroutineScope, GlobalScope 안에서 동작해야 함 delay
코드 추가 CoroutineScope(/* thread type */).launch {}로 실행 launch {}의 return job의 동작을 지정 가능 join() : scope 동작이 끝날때까지 대기하며, suspend에서 호출 가능 cancel() : 동작을 종료하도록 호출 start() : scope이 아직 시작하지 않을 경우 start, scope의 상태를 확인 CoroutineScope 중요한 점
(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 }
Application의 lifetime에 따라 동작하는 scope에서 사용 추천 GlobalScope는 Dispatchers.Unconfined(worker thread)에서 동작 GlobalScope.launch(/* thread type */) {}로 실행 GlobalScope
routine의 취소, 실행, 종료를 대기할 수 있다 job.cancel() : 종료하도록 유도한다 job.join() : 종료를 대기하며 호출은 suspend에서 가능하다 job.start() : coroutine의 시작을 확인할 수 있으며, 시작 상태라면 true Job
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
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!
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가 포함되어야 한다
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를 정의한다.
간결 및 자동으로 종료 처리 해준다. Default 상위 Activity에서 정의한 CoroutineScope의 Thread를 활용한다 필요시 launch, actor에서 Thread를 언제든 변경 가능하다 다양한 CoroutineScope 구현 ViewModel LifecycleObservable Fragment CoroutineScope을 상속받아 구현 Android Coroutines
처리할 수 있다. Retrofit2 kotlin coroutines adapter Android Coroutines interface RetrofitService { @GET("/posts") fun getPosts(): Deferred<Response<List<Post>>> }
= request.await() if (response.isSuccessful) { // Pass in the response.body() to your adapter } else { toast("Error ${response.code()}") } } Retrofit2 kotlin coroutines adapter