async/awaitで快適非同期ライフ

 async/awaitで快適非同期ライフ

2017/04/04に行われた第5回Kotlin勉強会@Sansanの発表資料です。

0f8842fabcd31a6c9007ddcd648247db?s=128

Keita Kagurazaka

April 04, 2017
Tweet

Transcript

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

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

  3. async/awaitがない世界

  4. アプローチ1: 完了時コールバック fun doSomething(completed: () -> Unit) { // 非同期処理

    completed() } fun main(args: Array<String>) { doSomething { println(“Done!”) } // ... }
  5. もっと複雑な処理だと……

  6. タスクA→タスクB→タスクC→タスクD (直列) fun main(args: Array<String>) { taskA { taskB {

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

    taskC { taskD { println(“Done!”) } } } } // ... } コールバック地獄
  8. アプローチ2: ReactiveX (or Promise) fun main(args: Array<String>) { taskA().andThen(taskB) .andThen(taskC)

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

    .andThen(taskD) .subscribe { println(“Done!”) } // ... } 慣れれば読める
  10. async/await fun main(args: Array<String>) { async(CommonPool) { taskA().await() taskB().await() taskC().await()

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

    taskD().await() println(“Done!”) } // ... } async/awaitを除くと 同期的な書き方と同じ!
  12. どういう仕組みなのか?

  13. コルーチン • 途中で中断・再開できるサブルーチン(関数)の一般化 • 中断するので呼び出し元スレッドをブロックしない fun main(args: Array<String>) { async(CommonPool)

    { // コルーチン作成 taskA().await() // awaitでコルーチンを中断、完了したらここから再開 delay(1000) // コルーチンを中断、1000ms後にここから再開 taskB().await() // awaitでコルーチンを中断、完了したらここから再開 println(“Done!”) } // ... }
  14. コルーチンの作り方

  15. よく使うコルーチンビルダー ビルダー launch async<T> runBlocking<T> 戻り値 Job Deferred<T> T 使い方

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

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

  18. Coroutine ContextとCoroutine Dispatcher • Coroutine Contextとはコルーチンをどのスレッドで実行する かを決定するもの • Coroutine Dispatcherとはコルーチンの実行を特定のスレッド

    上に制限する仕組み • CoroutineDispatcher : CoroutineContext
  19. よく使うコルーチンコンテキスト • CommonPool ◦ コルーチンをスレッドプール上で実行するdispatcher ◦ ForkJoinPoolがあればそのcommonPoolを使う ◦ なければExecutors.newFixedThreadPoolをコア数-1スレッドで作成 ◦

    スレッド数指定はできません • UI系 ◦ 各プラットフォームのUIスレッド上でコルーチンを実行するdispatcher ▪ UI (Android) ▪ JavaFx (JavaFx) ▪ Swing (Swing)
  20. コード例1: 非同期処理をmainで呼ぶ fun asyncDoHeavyWork() = async(CommonPool) { // 処理してIntを返す }

    fun main(args: Array<String>) = runBlocking<Unit> { val result = asyncDoHeavyWork().await() println(“Result: ${result * 2}”) }
  21. コード例2: カウントダウン fun onStartCountDownButtonClick() = launch(UI) { for (count in

    10 downTo 1) { countDownTextView.text = “Count: $count” delay(1000) } countDownTextView.text = “Start!” }
  22. コルーチンってどこで中断されるの?

  23. Suspending Function & Lambda • 呼び出されると呼び出し元コルーチンが中断される • 完了すると中断したところから再開する • suspending関数はコルーチンかsuspending関数からしか呼

    び出せない • 今まで出てきたjoin、await、delayはsuspending関数 • コルーチンビルダーのブロックはsuspendingラムダ
  24. async/await使いたくなってきましたか?

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

  26. Rx連携 fun asyncDoHeavyWork(): Single<Int> = Single.create { // 処理 it.onSuccess(result)

    }.subscribeOn(Schedulers.io()) fun main(args: Array<String>) = runBlocking<Unit> { val result = asyncDoHeavyWork().await() println(“Result: ${result * 2}”) }
  27. Rx連携 fun asyncDoHeavyWork(): Single<Int> = rxSingle(CommonPool) { // 処理 return@rxSingle

    result } fun main(args: Array<String>) = runBlocking<Unit> { val result = asyncDoHeavyWork().await() println(“Result: ${result * 2}”) }
  28. 複雑な例で威力を確かめる

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

  30. 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にしなければいけない • 見た目がグロい
  31. async/awaitの場合 fun taskD(aDeferred: Deferred<Int>) = async(CommonPool) { // 処理 val

    a = aDeferred.await() // aが必要になったタイミングでawait // aが必要な処理 return@async result }
  32. async/awaitの場合 fun taskD(aDeferred: Deferred<Int>) = 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()
  33. async/awaitの場合 fun taskD(aDeferred: Deferred<Int>) = 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() ほとんど同期と変わらない!!!
  34. まとめ • async/awaitは非同期処理を同期的に書けるパターン • 呼び出し元をブロックしないので、計算資源をより有効に使用 可能 • 必要になるまでawaitを遅延させることで、勝手に並行処理が 最適化 •

    Rxと連携も可能
  35. Thanks!