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

coroutinesで非同期ページネーション

 coroutinesで非同期ページネーション

2017/06/29に開催された第6回Kotlin勉強会 @ Sansanの発表スライドです。

Keita Kagurazaka

June 29, 2017
Tweet

More Decks by Keita Kagurazaka

Other Decks in Programming

Transcript

  1. 自己紹介 • アカウント ◦ Twitter: @kkagurazaka ◦ Github: k-kagurazaka •

    Sansan Android developer • Kotlin Love! RxProperty for Android https://github.com/k-kagurazaka/rx-property-android AsyncPermissions https://github.com/k-kagurazaka/async-permissions
  2. coroutines (コルーチン) とは • 中断・再開可能な関数のようなもの ◦ Threadクラスのように作成して、実行する • 中断している間はスレッドをブロックしない ◦

    non-blocking • 特定のスレッドに紐付かない ◦ 中断前と再開後で別スレッドで動かせる • 中断時・完了時に値を返せる
  3. コルーチンの作成 ビルダー launch async<T> 戻り値 Job Deferred<T> 使い方 val job

    = launch(CommonPool) { // 処理 } job.join() val deferred = async(CommonPool) { // T型を返す処理 } val result = deferred.await() 説明 値を返さないコルーチンを作成するビル ダー。 Jobをjoinすると完了まで中断、 cancelで キャンセルできる。 T型の値を返すコルーチンを作成するビル ダー。 Deferred<T>をawaitすると完了まで中断、 cancelでキャンセルできる。
  4. 中断点 = suspending function / lambda • suspending function ◦

    suspend fun hoge() { } • suspending lambda ◦ val lambda: suspend () -> Unit = { } • suspending function / lambda は suspending function / lambda からしか呼べない
  5. スレッドの切り替え suspend fun <T> run(context: CoroutineContext, block: suspend () ->

    T): T • 使い方 suspend fun loadItems() { val items = run(CommonPool) { getItems() } run(UI) { itemList.addAll(items) } } suspend fun taskAonUI() = run(UI) { // 処理 }
  6. コルーチンの中断と再開例 // コルーチン作成&開始 launch(CommonPool) { val hoge = ... taskAonUI()

    // UIスレッドでtaskAを実行中はこのコルーチンは中断 val huga = … // taskAが完了するとここからコルーチンを再開 } 中断している間は、コルーチンはスレッドをブロックしない • taskAの実施中に別のコルーチンがスレッドを使える • taskA完了後もまだ使われてたら別のスレッドで再開する
  7. Channelとは • ブロックの代わりに中断するBlockingQueue • suspend fun send(value: T) ◦ Channelのcapacityに空きがあれば送信

    ◦ なければ空くまで中断 • suspend fun receive(): T ◦ Channelに値があれば受信 ◦ なければ値が来るまで中断 capacityが0の場合はsendとreceiveが揃ったら送受信
  8. コルーチン間でのデータ受け渡し val channel = Channel<Int>() // デフォルトではcapacity = 0のチャンネル launch(CommonPool)

    { repeat(5) { println(channel.receive()) } // 5回受信 } launch(CommonPool) { repeat(10) { channel.send(it) } // 0から4まで送信、5の送信時に中断 }
  9. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    { // 受信 (receive) 専用 Channel val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
  10. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    { val channel = Channel<Book>() // capacity = 0 の Channel 作成 launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
  11. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    { val channel = Channel<Book>() launch(CommonPool) { // スレッドプールで動くコルーチン起動 var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
  12. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    { val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel // 作ったチャンネルを返す }
  13. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    { val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { // 無限ループで val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
  14. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    { val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) // ページ数をincrementしながら取得 if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
  15. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    { val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } // 取得したものを Channel に送信 if (books.size < LIMIT) break } channel.close() } return channel }
  16. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    { val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break // 空か books.forEach { channel.send(it) } if (books.size < LIMIT) break // LIMIT より少なかったら無限ループから抜ける } channel.close() } return channel }
  17. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    { val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } // 受信されないかぎりここで中断 if (books.size < LIMIT) break } channel.close() } return channel }
  18. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    = produce(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { send(it) } if (books.size < LIMIT) break } } 簡単に書けるメソッドもあるよ!
  19. suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>

    = produce(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { send(it) } if (books.size < LIMIT) break } } // 20個読み込む (LIMIT < 20でも問題なし) repeat(20) { val book = channel.receiveOrNull() ?: return@repeat }