December 22, 2019

Kotlin/NativeでBackground Threadを使う

この記事はKotlin Advent Calendar 2019 の22日目の記事です。

Kotlin Advent Calendarに参加するのは今年で5年連続5回目になりました🎉🐦
毎年22日近辺を書いています。マイルストーンの時から書いている記事もあるので情報が古くなっているものもありますが、 過去にはこんな記事を書いていました。

Overview

Kotlin Multiplatform Project(MPP)は先日行われたKotlin Conf 2019 でも、JetBrainsは大々的にMPPを推していたので、より一層この流れは加速していくものと思われます。
私は昨年のKotlin Conf 2018に参加した際に聞いたMPPのセッションが衝撃で、ここ1年間ぐらいはMPPの情報を追い続けています。 今月には、現職で携わっているiOSとAndroidアプリのログ実装部分を共通化したMPPモジュールを取り入れており、まもなく本番環境に投入予定です。(iOSは既にリリースされています) その件に関しては別途会社のブログで報告したいと思います。

しかし、まだKotlin/Nativeはベータバージョンとなっています。 Kotlin/Nativeが正式リリースを迎えるには、マルチスレッドでの動作は必須となっており、リリース当初から1番の問題と言っても過言ではありません。
ところが、長らく弱点とされていたKotlin/Nativeにおけるマルチスレッドの問題が、つい最近急激に進歩をみせたため、この記事ではKotlin/Nativeにおけるマルチスレッド処理を取り上げたいと思います。

Kotlin/Native 最大の弱点

根本的にはKotlin/Nativeのメモリ管理による問題なのですが、昨今のアプリケーション開発においては欠かせない非同期処理の部分を担っているkotlinx.Coroutinesメインスレッドでのみしか動作しないという最大の弱点がありました。
こちらの問題はCoroutinesのリポジトリ内のこちらのIssue にて議論されています。

長らく将来対応予定のステータスままだったのですが、ついに今年の10月にHierarchical MPP(HMPP)の一部として対応が進んでいる事が発表されました
Kotlin/Native 1.3.60のリリースに合わせて、Coroutines version 1.3.2-native-mt-1として最初のSNAPSHOTが公開されました。さらに12/19(木) に1.3.3-native-mtも新たに公開されました。この記事では、このバージョンを用いて説明をしていきます。
この他に、今回利用しているライブラリは以下のようになっています。

ライブラリバージョン
Kotlin1.3.61
org.jetbrains.kotlinx:kotlinx-coroutines1.3.3-native-mt

おさらい

御存知の通り、Kotlin/Nativeはメインスレッドでのみしか動作しないという問題がありました。
一応Kotlin/Nativeにのみスレッドの代わりにWorkerという仕組みがあるのですが、異なるWorker間でオブジェクトを共有する場合は必ずfreeze()(Kotlin/Nativeのみ)のAPIを使ってImmutableのオブジェクトに変換する必要があります。

class Hoge

val hoge = Hoge()

// freeze in only Kotlin/Native
hoge.freeze()

またWorkerではないバックグラウンドのスレッドでは動作しません。

GlobalScope.launch(Dispathcers.Default) {
   println("Hello World from a background thread.")  // Error
}

このように、現状のKotlin/Nativeでは非同期処理に大きな制約があります。

基本的な使い方

使い方と言ってもCoroutinesのバージョンが変わったのみで、通常の使い方と変わりません。

Dispatcherの宣言

Kotlin/Native側でバックグラウンドスレッド用のCoroutineDispatcherを用意してあげる必要があります。 今回commonMainの部分でスレッドの切り替えを行いたいため、expectで定義します。

  • Common
internal expect val backgroundDispatcher: CoroutineDispatcher
  • iOS
internal actual val backgroundDispatcher: CoroutineDispatcher = Dispatchers.Default

自分でスレッドの指定をしたい場合は、newSingleThreadContextでスレッド名を指定することが出来ます。 Dispatchers.Defaultでも中で同様にnewSingleThreadContext("DefaultDispatcher")でスレッドを生成しているため、 こだわりがなければDispatchers.Defaultで良いと思います。

ちなみに今までは、Kotlin/NativeでのCoroutineDispatcherを使う場合はdispatch_get_main_queue()を使って、 必ずメインスレッドを指定してあげる必要がありました。

  • Android
internal actual val backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO

Androidは通常と変わらず好きなDispatcherを指定することが可能です。 ここではDBの操作を例にするため、Dispatchers.IOを指定します。

Dispatcherの切り替え

特にKotlin/Native特有の記法があるわけではなく、そのままCoroutinesのwithContext(CoroutineContext)を使ってスレッドの切り替えをする事が出来ます。
Coroutinesでは特にContextの指定をしなければ、呼び出し元のCoroutines Scope(Context)が適用されますので、例えばGlobalScopeやAndroidにあるlifecycleScope等で起動した場合は

lifecycleScope.launch {
  // in the main thread
  val result = withContext(backgroundDispatcher) {
      // in a background thread 
  }                                        
  // to the main thread
  result // on the main thread
}

このようにしてスレッドの切り替えが可能です。
呼び出しのスレッドはメインスレッドになっていて、withContextのブロック内でバックグラウンドの処理をしてブロックを抜けた段階で また元のメインスレッドに戻っています。

ただ1つKotlin/Native用のCoroutinesには注意があって、withContext()やバックグラウンドスレッドでは気を効かせて、ブロック内を再帰的にfreezeされたImmutableオブジェクトに変換してくれます。

val result = withContext(backgroundDispatcher) { }

println(result.isFrozen) // true

もしfreezeして欲しくない場合は、ensureNeverFrozen()を呼び出すだけでfreezeされずに済みます。 ensureNeverFrozen()は再帰的に呼び出されるため、呼び出し元のクラスでもensureNeverFrozen()を宣言されると、FreezingExceptionが起きてしまうので注意しましょう。

これだけの記述でKotlin/Nativeでも通常のCorutines同様にバックグラウンドの処理が出来るようになります。
ただ現状はまだ問題があり、例えば異なるスレッドで同時に動作している子コルーチンが同時にキャンセルされるとメモリリークを起こしてしまいますので注意しましょう。
Coroutinesのバックグラウンドのドキュメントはこちらにあります。

kotlinx.coroutines/kotlin-native-sharing.md at native-mt · Kotlin/kotlinx.coroutines

Library support for Kotlin coroutines . Contribute to Kotlin/kotlinx.coroutines development by creating an account on GitHub.

まとめ

Kotlin/Nativeでも遂にマルチスレッドが使えるようになりました!!
とはいえ、まだEarly Preview版の立ち位置ですので本番環境で使うのはもう少しかかりそうですが、着実に実用段階に向けて歩んでいます。 2020年にはFlutterやReact Nativeと肩を並べてクロスプラットフォーム開発の選択肢の1つに上がるくらいになると個人的には嬉しいです。

今年もKotlinにとって素晴らしい一年でした。それではよいお年を!
Have a nice Kotlin!🐦

続きを書いたのでZehi!

オレの考えた最強のKotlin Multiplatform Projectアーキテクチャ2020 - AAbrain

Kotlin/Nativeがバックグラウンドに対応したので、Flowを使ってKotlin Multiplatform Projectで考えてる設計を語ります

宣伝

ついに、半年ぐらい前から共著で書いていた本が来年1月末頃に発売されます!
技術評論社から出版されている「みんなのシリーズ」の1つとして、タイトルは「みんなのKotlin」です。
私は4章のMPPの章を担当しました。
MPPはまだβ版で書けるところが少なく、Kotlin Festの発表を聞いて頂いた方は大体聞いたことある内容となっているので、 物凄く参考になるかと言われると微妙なのですが、発表では触れられなかった箇所にもいくつか触れているのでお布施ぐらいの気持ちで買って頂けるとありがたいです。(サンプルも書いたりしたらページ数では一番多くなってしまったっぽい)
もちろん、他の章にはAndroid, Server, Test等 一通りKotlinの文法を覚えて、いざ業務で使うという時に参考になる内容が多いので、興味がある方は是非お手元にどうぞ。
Kindle版はそのうち出ると思います。

© AAkira 2023