Slide 1

Slide 1 text

async/awaitで 快適非同期ライフ 2017/4/4 第5回Kotlin勉強会@Sansan @kkagurazaka

Slide 2

Slide 2 text

async/awaitとは 非同期処理をあたかも同期処理のように書けるパターン いくつかの言語では既に実現 ● C# ● JavaScript ● Python など

Slide 3

Slide 3 text

async/awaitがない世界

Slide 4

Slide 4 text

アプローチ1: 完了時コールバック fun doSomething(completed: () -> Unit) { // 非同期処理 completed() } fun main(args: Array) { doSomething { println(“Done!”) } // ... }

Slide 5

Slide 5 text

もっと複雑な処理だと……

Slide 6

Slide 6 text

タスクA→タスクB→タスクC→タスクD (直列) fun main(args: Array) { taskA { taskB { taskC { taskD { println(“Done!”) } } } } // ... }

Slide 7

Slide 7 text

タスクA→タスクB→タスクC→タスクD (直列) fun main(args: Array) { taskA { taskB { taskC { taskD { println(“Done!”) } } } } // ... } コールバック地獄

Slide 8

Slide 8 text

アプローチ2: ReactiveX (or Promise) fun main(args: Array) { taskA().andThen(taskB) .andThen(taskC) .andThen(taskD) .subscribe { println(“Done!”) } // ... }

Slide 9

Slide 9 text

アプローチ2: ReactiveX (or Promise) fun main(args: Array) { taskA().andThen(taskB) .andThen(taskC) .andThen(taskD) .subscribe { println(“Done!”) } // ... } 慣れれば読める

Slide 10

Slide 10 text

async/await fun main(args: Array) { async(CommonPool) { taskA().await() taskB().await() taskC().await() taskD().await() println(“Done!”) } // ... }

Slide 11

Slide 11 text

async/await fun main(args: Array) { async(CommonPool) { taskA().await() taskB().await() taskC().await() taskD().await() println(“Done!”) } // ... } async/awaitを除くと 同期的な書き方と同じ!

Slide 12

Slide 12 text

どういう仕組みなのか?

Slide 13

Slide 13 text

コルーチン ● 途中で中断・再開できるサブルーチン(関数)の一般化 ● 中断するので呼び出し元スレッドをブロックしない fun main(args: Array) { async(CommonPool) { // コルーチン作成 taskA().await() // awaitでコルーチンを中断、完了したらここから再開 delay(1000) // コルーチンを中断、1000ms後にここから再開 taskB().await() // awaitでコルーチンを中断、完了したらここから再開 println(“Done!”) } // ... }

Slide 14

Slide 14 text

コルーチンの作り方

Slide 15

Slide 15 text

よく使うコルーチンビルダー ビルダー launch async runBlocking 戻り値 Job Deferred T 使い方 val job = launch(context) { // 処理 } job.join() val deferred = launch(context) { // T型を返す処理 } val result = deferred.await() fun main(args: Array) = runBlocking { // 処理 } 説明 結果を返さないコルーチンを作 成するビルダー。 Jobをjoinすると完了まで中断、 cancelでキャンセルできる。 T型の結果を返すコルーチンを作 成するビルダー。 Deferredをawaitすると完了 まで中断、cancelでキャンセルで きる。 呼び出しスレッドをブロックする コルーチンを作成するビル ダー。 main関数やテストメソッドなどが 主な活躍場所。

Slide 16

Slide 16 text

よく使うコルーチンビルダー ビルダー launch async runBlocking 戻り値 Job Deferred T 使い方 val job = launch(context) { // 処理 } job.join() val deferred = launch(context) { // T型を返す処理 } val result = deferred.await() fun main(args: Array) = runBlocking { // 処理 } 説明 結果を返さないコルーチンを作 成するビルダー。 Jobをjoinすると完了まで中断、 cancelでキャンセルできる。 T型の結果を返すコルーチンを作 成するビルダー。 Deferredをawaitすると完了 まで中断、cancelでキャンセルで きる。 呼び出しスレッドをブロックする コルーチンを作成するビル ダー。 main関数やテストメソッドなどが 主な活躍場所。

Slide 17

Slide 17 text

コルーチンコンテキスト

Slide 18

Slide 18 text

Coroutine ContextとCoroutine Dispatcher ● Coroutine Contextとはコルーチンをどのスレッドで実行する かを決定するもの ● Coroutine Dispatcherとはコルーチンの実行を特定のスレッド 上に制限する仕組み ● CoroutineDispatcher : CoroutineContext

Slide 19

Slide 19 text

よく使うコルーチンコンテキスト ● CommonPool ○ コルーチンをスレッドプール上で実行するdispatcher ○ ForkJoinPoolがあればそのcommonPoolを使う ○ なければExecutors.newFixedThreadPoolをコア数-1スレッドで作成 ○ スレッド数指定はできません ● UI系 ○ 各プラットフォームのUIスレッド上でコルーチンを実行するdispatcher ■ UI (Android) ■ JavaFx (JavaFx) ■ Swing (Swing)

Slide 20

Slide 20 text

コード例1: 非同期処理をmainで呼ぶ fun asyncDoHeavyWork() = async(CommonPool) { // 処理してIntを返す } fun main(args: Array) = runBlocking { val result = asyncDoHeavyWork().await() println(“Result: ${result * 2}”) }

Slide 21

Slide 21 text

コード例2: カウントダウン fun onStartCountDownButtonClick() = launch(UI) { for (count in 10 downTo 1) { countDownTextView.text = “Count: $count” delay(1000) } countDownTextView.text = “Start!” }

Slide 22

Slide 22 text

コルーチンってどこで中断されるの?

Slide 23

Slide 23 text

Suspending Function & Lambda ● 呼び出されると呼び出し元コルーチンが中断される ● 完了すると中断したところから再開する ● suspending関数はコルーチンかsuspending関数からしか呼 び出せない ● 今まで出てきたjoin、await、delayはsuspending関数 ● コルーチンビルダーのブロックはsuspendingラムダ

Slide 24

Slide 24 text

async/await使いたくなってきましたか?

Slide 25

Slide 25 text

でもウチでは非同期は Rxだから……という方にも!

Slide 26

Slide 26 text

Rx連携 fun asyncDoHeavyWork(): Single = Single.create { // 処理 it.onSuccess(result) }.subscribeOn(Schedulers.io()) fun main(args: Array) = runBlocking { val result = asyncDoHeavyWork().await() println(“Result: ${result * 2}”) }

Slide 27

Slide 27 text

Rx連携 fun asyncDoHeavyWork(): Single = rxSingle(CommonPool) { // 処理 return@rxSingle result } fun main(args: Array) = runBlocking { val result = asyncDoHeavyWork().await() println(“Result: ${result * 2}”) }

Slide 28

Slide 28 text

複雑な例で威力を確かめる

Slide 29

Slide 29 text

複雑な待ち合わせ 並列可能なところは並列に処理したい

Slide 30

Slide 30 text

Rxの場合 val streamA = taskA().toObservable().share() // hot Observable化 val streamB = taskB().toObservable() val streamC = streamA.zipWith(streamB) { a, b -> taskC(a, b) } .flatMap { it.toObservable() } val streamD = streamA.flatMap { task.D(it).toObservable() } val streamE = streamC.zipWith(streamD) { c, d -> taskE(c, d) } .flatMap { it.toObservable() } streamE.subscribe { val result = it } ● taskAの結果が2箇所で使われるのでhot Observableにする必要がある ● operatorの関係で全体的にSingle -> Observableにしなければいけない ● 見た目がグロい

Slide 31

Slide 31 text

async/awaitの場合 fun taskD(aDeferred: Deferred) = async(CommonPool) { // 処理 val a = aDeferred.await() // aが必要になったタイミングでawait // aが必要な処理 return@async result }

Slide 32

Slide 32 text

async/awaitの場合 fun taskD(aDeferred: Deferred) = async(CommonPool) { // 処理 val a = aDeferred.await() // aが必要になったタイミングでawait // aが必要な処理 return@async result } val a = taskA() val b = taskB() val c = taskC(a, b) val d = taskD(a) val e = taskE(c, d) val result = e.await()

Slide 33

Slide 33 text

async/awaitの場合 fun taskD(aDeferred: Deferred) = async(CommonPool) { // 処理 val a = aDeferred.await() // aが必要になったタイミングでawait // aが必要な処理 return@async result } val a = taskA() val b = taskB() val c = taskC(a, b) val d = taskD(a) val e = taskE(c, d) val result = e.await() ほとんど同期と変わらない!!!

Slide 34

Slide 34 text

まとめ ● async/awaitは非同期処理を同期的に書けるパターン ● 呼び出し元をブロックしないので、計算資源をより有効に使用 可能 ● 必要になるまでawaitを遅延させることで、勝手に並行処理が 最適化 ● Rxと連携も可能

Slide 35

Slide 35 text

Thanks!