Kotlin Coroutine #2



reference: 

https://hellsoft.se/simple-asynchronous-loading-with-kotlin-coroutines-f26408f97f46





 Coroutine Context

이전 블로그에서 했던 launch {}는 임의의 thread의 coroutine에서 작업을 수행합니다. 만약 작업이 수행될 coroutine을 명시하고 싶다면 어떻게 해야할까요. 여기서 제공되는 것이 coroutine context입니다.


 launch with context

아래 예제에서는 content resolver로부터 어떤 이미지를 불러온 후, view에 반영하는 작업입니다.

당연히 이미지를 불러오는 작업은 disk IO이기 때문에 main thread에서는 피하는 게 좋겠죠.

val job = launch(Background) {
val
uri = Uri.withAppendedPath(imagesBaseUri, imageId.toString())
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,
launch(UI) {
imageView.setImageBitmap(bitmap)
}
}

launch(Background)에서 Background는 무엇일까요? 

바로 CoroutineContext입니다. 

아래처럼 만들어 주면 됩니다. 2개의 fixed size를 갖는 thread pool로부터 CoroutineContext를 가져왔습니다. 이름은 "bg"네요.

internal val Background = newFixedThreadPoolContext(2, "bg")


그렇다면 아래의 UI는 무엇인가요? 네, 물론 Android의 main thread입니다. 애초에 이렇게 제공을 해주네요. 선언은 아래와 같이 돼있었습니다.

val UI = HandlerContext(Handler(Looper.getMainLooper()), "UI")


Conclusion

Thread나 Executor 등을 통해 concurrent programming을 하는 것이 Java에서는 아주 일반적이지만, Kotlin에서는 다른 방식을 제안하려고 하는 것 같습니다. Coroutine은 light-weight thread로 설명할 수 있으며, block 대신 suspend를 하기 때문에 thread에 비해 switching 비용이 매우 적습니다. Continuation Passing Style을 통해 그것이 가능하다고 합니다.

 여기에서 참고한 글을 정리했습니다.


Q. Coroutine vs thread, 언제 어떤 것을 사용해야 하나?

A. Coroutine는 다른 작업을 기다리는 용도의 비동기 작업을, thread는 CPU-intensive한 작업을 할 때 사용하면 된다.


Q. Light-weight thread라는 설명은 적절치 않아 보인다, 왜냐면 coroutine은 thread pool로 부터 thread를 가져와서 그위에 실행을 하니까. 차라리 "task"라는 설명이 더 와닿지 않느냐? (Thread 위의 Runnable 정도의 의미인 것 같네요)

A. Light-weight thread라는 표현은 표면상의 의미고, 개발자 본인이 보기에 따라 thread도 마찬가지 이듯 다른 의미가 될 수 있을 것 같다.


Q. Coroutine이 thread와 비슷하다면, 서로 다른 coroutine 간에 공유되는 상태들에 대한 동기화 작업이 필요 할 것 같다.

A. Coroutine에서는 가급적이면 가변한 공유 상태를 가지기를 추천하지 않는다. 


'programming > android' 카테고리의 다른 글

기본적인 메모리 관리  (0) 2018.03.06
Splash 화면 구성하기  (3) 2018.02.20
Kotlin Coroutines #1  (1) 2018.01.27
Tools로 xml layoyt의 preview 제대로 표시하기  (0) 2018.01.22
Kotlin Standard Functions  (0) 2018.01.22

Kotlin Coroutines #1



reference: 

https://kotlinlang.org/docs/reference/coroutines.html

https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html





 Coroutines

Android의 main thread에서는 UI 조작만을 권장하기 때문에(UI가 멈추는걸 좋아하는 사용자는 없겠죠), disk IO나 http request 같은 작업은 다른 thread로 부터 수행되어야 합니다. 일반적인 경우라면 thread with handler나 AsyncTask를 사용하게 될 텐데요, 이해는 됩니다만 적어야 하는 소스의 양이 많아 지죠. 이런 비동기 작업들을 좀 더 효율적으로 작성하기 위해 언어 차원에서 제공되는 기능으로 보입니다. 


 Suspend

여러 작업들을 하다보면 잠시 중단하여 다른 작업을 해야할 때가 있습니다. Coroutine은 thread 위에서 동작하며, thread를 block하지 않고 suspend합니다. Thread blocking 및 context switch는 비용이 비싸기 때문에 간단한 작업을 하려는 것에 비해 지나친 시간이 소요되곤 합니다. 반면 coroutine의 suspend는 거의 cost-free하다고 합니다.

(https://stackoverflow.com/questions/43021816/difference-between-thread-and-coroutine-in-kotlin)


 launch {}

간단한 예제로 실습해보았습니다.

@Test fun corountine_test() {
println("start")

// do things on default coroutine(shared pool of threads)
launch {
// delay doesn't stop the thread, but only suspends the coroutine itself (non-blocking)
// when coroutine is being suspended, the thread is returned to the pool till it's needed again
// and when coroutine is done waiting, it finds a free thread in the pool and resumes
delay(1000)
println("hello")
}

// when we want to do delay on main thread
runBlocking {
delay(2000)
}
println("stop")
}

Coroutine은 launch로 시작되며, 명시하지 않으면 기본적으로 shared thread pool의 임의의 thread에서 수행됩니다. 

delay()는 앞서 언급한 suspend의 기능을 수행합니다. Thread.sleep()의 전통적인 방식에 비해 매우 적은 자원을 소모하고, thread를 block하지 않습니다.

Coroutine이 suspend되면 수행되던 thread는 다시 pool로 반환되며, 필요해질 때 다시 불러진다고 합니다. Thread pool 관리 차원에서도 효율적이라고 볼수 있겠습니다. Suspend가 종료되면, 다시 pool에서 가용한 thread를 찾아 작업을 재개합니다.

또한 delay()는 coroutine에서만 사용할 수 있기 때문에, 일반적인 thread 상의 코드에서는 사용이 불가합니다. 따라서 위와 같이 runBlocking {}을 통해 호출하여야 합니다.


Cost

아래 예시로 coroutine의 효율을 간단하게 알아보았습니다:

@Test fun is_coroutine_cheaper() {
val c = AtomicInteger()
for (i in 1..1_000_000) {
launch {
c.addAndGet(i)
}
}
// not all of coroutines are completed, c is not what we expect
println(c.get())
}

위 코드의 수행시간은 1초 미만이었습니다. 100만개의 coroutine을 생성하여 작업하였으나 매우 적은 시간이 소요되었음을 알 수 있었는데요, thread로 수행한다면 분명 수 분의 시간이 소요됐을 것 같습니다.


 async {}

async {}는 기본적으로 launch {}와 같은 동작을 하지만 Deffered<T>를 반환합니다. (Deffered<T>는 아주 간단한 future라고 합니다)

@Test fun async_test() {
val deferred: List<Deferred<Int>> = (1..1_000_000).map { n ->
async {
delay(1000) // to make sure coroutines run parallel
n
}
}

runBlocking {
// await has to be called in coroutine
val sum = deferred.sumBy { it.await() }
println("sum : $sum")
}
}

위 예제는 앞서와 같이 100만개의 async {} 를 통한 coroutine을 수행하는 코드입니다. 다만 여기서의 차이점은 저 작업들이 완료되기까지 기다린다는 점인데요, await()를 통해 작업이 완료되기 까지 기다리게 하였습니다. 

100만개의 coroutine들이 과연 parallel하게 수행되는지 확인하기 위하여 각 작업마다 1초를 delay했습니다. 수행시간은 10초가 소요되지 않았습니다.

역시, coroutine의 기능은 coroutine 안에서 호출되어야 하므로 await()는 runBlocking {}안에서 수행했습니다. 


Suspend function

만약 위 예제에서 async {}안의 작업들을 하나의 함수로 표현하고 싶다면?

async {
workload(n)
}

fun workload(n: Int): Int {
delay(1000)
return n
}

위와 같은 코드가 될 것 같습니다. 하지만 앞서 말한 바와 같이, delay()는 coroutine의 기능이기 때문에 일반적인 함수에서는 호출이 불가합니다. 컴파일이 되지 않네요.

이럴 때 쓰는 것이 suspend 키워드입니다. fun 앞에 suspend를 붙이기만 하면 됩니다.


suspend fun workload(n: Int): Int {
delay(1000)
return n
}


'programming > android' 카테고리의 다른 글

기본적인 메모리 관리  (0) 2018.03.06
Splash 화면 구성하기  (3) 2018.02.20
Kotlin Coroutines #2  (0) 2018.01.28
Tools로 xml layoyt의 preview 제대로 표시하기  (0) 2018.01.22
Kotlin Standard Functions  (0) 2018.01.22

Tools on XML layout



reference: 

https://blog.stylingandroid.com/tool-time-part-1-2/?utm_source=Android+Weekly&utm_campaign=97507a2973-EMAIL_CAMPAIGN_2018_01_21&utm_medium=email&utm_term=0_4eb677ad19-97507a2973-338124565





RecyclerView 등과 같은 list는 런타임에서 모양이 정해지기 때문에, 

xml에 작성하면 preview 패널에선 무조건 vertical list로 표시되며 simple string의 dummy item 항목들이 보입니다.

실제 소스는 전혀 그렇지 않음에도 그렇게 표시된다면 상당히 불편하죠. 

그럴 때 쓰는게 Tools namespace입니다. 주요한(내가 쓸 것 같은) 것을 예제로 적었습니다.

Tools는 실제 구현과 무관하게 preview에서 어떻게 보여질 지만을 정해줌으로써

preview를 참고하여 디자인하는데 도움을 줍니다.




<android.support.v7.widget.RecyclerView
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/id_quiz_recycler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
tools:listitem="@layout/card_quiz"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

위 예제는 아래와 같은 preview를 갖습니다. 내가 지정한 임의의 layout(간단한 CardView)을 item으로 표시해줍니다.


이 외에도 

tools:text

tools:src

를 통해 preview를 보완할 수 있습니다.


앞으로는 ListView를 구현 할 때 레이아웃 변경 때 마다 빌드하여 확인할 일이 없으면 좋겠네요....


'programming > android' 카테고리의 다른 글

기본적인 메모리 관리  (0) 2018.03.06
Splash 화면 구성하기  (3) 2018.02.20
Kotlin Coroutines #2  (0) 2018.01.28
Kotlin Coroutines #1  (1) 2018.01.27
Kotlin Standard Functions  (0) 2018.01.22

+ Recent posts