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

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

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

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

Keita Kagurazaka

April 04, 2017
Tweet

More Decks by Keita Kagurazaka

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. async/awaitがない世界

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  12. どういう仕組みなのか?

    View Slide

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

    View Slide

  14. コルーチンの作り方

    View Slide

  15. よく使うコルーチンビルダー
    ビルダー 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関数やテストメソッドなどが
    主な活躍場所。

    View Slide

  16. よく使うコルーチンビルダー
    ビルダー 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関数やテストメソッドなどが
    主な活躍場所。

    View Slide

  17. コルーチンコンテキスト

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. 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}”)
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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にしなければいけない
    ● 見た目がグロい

    View Slide

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

    View Slide

  32. async/awaitの場合
    fun taskD(aDeferred: Deferred) = async(CommonPool) {
    // 処理
    val a = aDeferred.await() // aが必要になったタイミングでawait
    // aが必要な処理
    [email protected] 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()

    View Slide

  33. async/awaitの場合
    fun taskD(aDeferred: Deferred) = async(CommonPool) {
    // 処理
    val a = aDeferred.await() // aが必要になったタイミングでawait
    // aが必要な処理
    [email protected] 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()
    ほとんど同期と変わらない!!!

    View Slide

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

    View Slide

  35. Thanks!

    View Slide