【Android】Kotlin でモダンな concurrency その4

Channel を使ってコールバック不要に

Channel の定義 (JetBrains のドキュメントより):

Channel は、概念的に BlockingQueue とよく似ています。主な違いの一つは、Put の代わりに「送信中断」をを持ち、Take の代わりに「受信中断」を持つことです。

Blocking Queues

Actor

Channel をシンプルに使えるツールが Actor です。

Actor は Handler と非常によく似ており、コルーチンのコンテキスト(つまり、アクションを実行するスレッド)を定義し、シーケンシャルに実行します。

コルーチンを使っており、キャパシティを決めて実行を中断することができます。

Actor は基本的に、処理をコルーチンチャンネルに転送します。実行順序と実行するコンテキストを限定することを保証します。

これで、synchronize は不要となり、すべてのスレッドはフリーです。


protected val updateActor by lazy {
  actor<Update>(UI, capacity = Channel.UNLIMITED) {
    for (update in channel) when (update) {
      Refresh -> updateList()
        is Filter -> filter.filter(update.query)
        is MediaUpdate -> updateItems(update.mediaList as List<T>)
        is MediaAddition -> addMedia(update.media as T)
        is MediaListAddition -> addMedia(update.mediaList as List<T>)
        is MediaRemoval -> removeMedia(update.media as T)
    }
  }
}

// 使い方
suspend fun filter(query: String?) = updateActor.offer(Filter(query))

この例では、実行するアクションを選択する際、sealed クラスを利用しています。


sealed class Update
object Refresh : Update()
class Filter(val query: String?) : Update()
class MediaAddition(val media: Media) : Update()

すべてのアクションはキューとなり、決してパラレルには実行されません。mutable なものを密閉するにはいい方法です。

(つづく)

【Android】Kotlin でモダンな concurrency その1
【Android】Kotlin でモダンな concurrency その2
【Android】Kotlin でモダンな concurrency その3


【Android】Kotlin でモダンな concurrency その3

Coroutine コンテキスト

Coroutine コンテキストでは、そのコードをどのスレッドで実行するか、exception がスローされたときの処理の方法、キャンセルを伝える親のコンテキストを定義します。


val job = Job()
val exceptionHandler = CoroutineExceptionHandler {
    coroutineContext, throwable -> whatever(throwable)
}

launch(CommonPool+exceptionHandler, parent = job) { ... }

job.cancel() は、それの保持しているすべての coroutine をキャンセルします。
そして exceptionHandler は、それらの中でスローされたすべて Exception を受け取ります。

(つづく)

【Android】Kotlin でモダンな concurrency その1
【Android】Kotlin でモダンな concurrency その2


Related Categories :  AndroidDevelopmemtKotlin


【Android】Kotlin でモダンな concurrency その2

Dispatch

Dispatch は coroutine のキーとなる概念で、スレッド間を移動するアクションとなります。

現在の Java でいう runOnUiThread と等価です。


public final void runOnUiThread(Runnable action) {
  if (Thread.currentThread() != mUiThread) {
    mHandler.post(action);
  } else {
    action.run();
  }
}

Kotlin Android でのUIコンテキストは、Handlerをベースとして dispatcher が実装されています、

以下のように使います。


launch(UI) { ... }

launch(UI, CoroutineStart.UNDISPATCHED) { ... }

launch(UI) では、Handler 内の Runnable に postされますので直接実行されません。

それに対して、launch(UI, CoroutineStart.UNDISPATCHED) は、そのスレッドですぐにラムダ部分を実行します。

UI は、メインスレッドが resume されたとき、coroutine がそれに対して dispatch されるのを保証しており、 メインルーパーに post するというネイティブAndroidの Handler が使われています。


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

(つづく)

【Android】Kotlin でモダンな concurrency その1


Related Categories :  AndroidDevelopmemtKotlin


【Android】Kotlin でモダンな concurrency その1

現在の Java/Android の concurrency フレームワークはコールバック地獄の原因となります。

それは、スレッドセーフを保証するシンプルな方法がないからです。

kotlin coroutine は、concurrency を管理するための効果的でシンプルなフレームワークです。

Suspending と Blocking

coroutine は、スレッドを置き換えるものではなく、それを管理するフレームワークのようなものです。

元のスレッドをブロックすることなく、バックグラウンド処理完了に対しての wait を可能にする実行コンテキストを定義しています。

コールバックを避けて、簡単に concurrency を行ってみましょう。

基本

最初は非常にシンプルな例です。UIコンテキストで coroutine を起動し、その中でIOコンテキストでイメージを取得します。

その後、UIコンテキストに戻ります。


launch(UI) {
    val image = withContext(IO) { getImage() } 
    imageView.setImageBitmap(image) 
}

シングルスレッドのように直感的なコードです。

getImage() がIOスレッドで実行されている間、メインスレッドは、自由に他の処理を行うことができます。

withContext は、その coroutine を getImage() が実行されている間は中断します。

getImage() のあとすぐに、メインlooperが利用可能になり、coroutine はメインスレッドを再開します。

そして imageView.setImageBitmap(image) が呼ばれます。

 

次の例は、2つのバックグラウンド処理を完了させ、その結果を利用する、というものです。

async/await を使ってパラレルに実行し、それら両方の結果取得のあとメインスレッドでそれを利用した処理を行います。


val job = launch(UI) {
    val deferred1 = async { getFirstValue() }
    val deferred2 = async(IO) { getSecondValue() }
    useValues(deferred1.await(), deferred2.await())
}

job.join() 

async は launch に似ていますが、deferred (Kotlin版 Future) を返します。

そして、await() で結果を取得します。パラメータなしで実行した場合は、CommonPool コンテキストで実行されます。

前の例と同様に、2つの結果を待っている間は、メインスレッドは自由に処理を行うことができます。

例にあるように、launch は処理が完了するのを待つのに利用できる Job を返します。

「スレッドをブロックせず coroutine をブロックする」ということを除けば、他の言語と同様です。

(つづく)


Android Architecture Blueprints での コルーチンの使われ方

良い記事がたくさんあります。

Kotlin の Coroutine を概観する

入門Kotlin coroutines

読んでみましたが、きちんと理解できてる自信がありません!

MVPの中で、どのように使われているかAndroid Architecture Blueprintsで見てみましょう。

todo-mvp-kotlin-coroutines

すべての非同期処理をコルーチンで置き換えます。シンプルな非同期プログラミングとなり、直感的にコード書くことができます。

 

Presenter

非同期処置の記述はここが起点になります。


private fun loadTasks(forceUpdate: Boolean, showLoadingUI: Boolean) = launchSilent(uiContext) {
    // ...
    val result = tasksRepository.getTasks()
    // ...
}

TasksPresenter.kt#L69

メインのデータをリポジトリから取得する部分です。


launchSilent(uiContext) {
  // ...
}

CoroutineExt.kt#L18-L25

と見慣れない コルーチンビルダーがありますが、元の形に戻すと、


launch(
  context = UI,
  start = CoroutineStart.DEFAULT,
  parent = null) {
  // ...
}

AppExecutors.kt#L32-L35

です。

Presenterでは、launch() でコルーチンの起点を作っています。

Viewのメソッドを利用しないメソッドでは、コルーチンコンテクストは、DefaultDispatcher となり省略することができます。

 

Repository


override suspend fun getTasks(): Result<List<Task>> {
  // ...
  val result = tasksLocalDataSource.getTasks()
  // ...
 }

TasksRepository.kt#L49-L69


override suspend fun getTasks(): Result<List<Task>> = withContext(appExecutors.ioContext) {
  // ...
  Result.Success(tasksDao.getTasks())
  // ...
}

TasksLocalDataSource.kt#L34-L41

Repository -> LocalDataSource とサスベンドなfunctionが深く呼ばれていきます。

LocalDataSource では、Presenterでのlauch()時のコルーチンコンテキストから、withContext(appExecutors.ioContext) として、すべて、ioContext としての DefaultDispatcher に一時的に切り替えています。

 

まとめ

ざっくり、コルーチン関連の入れ子関係を書いてみると、


// presenter
launch(context = UI, parent = null) {

  // repository
  withContext(DefaultDispatcher) {
    // fetch data from database
  }

}

Presenter で launch() して、Repository末端(local/remote) で withContext() でコンテクスト切り替え。

もちろん、コルーチン内から呼ばれるのは、repository の suspend な functionず。