
2025.02.12
職員一人あたり52時間の残業削減に成功 kintone導入がもたらした富士吉田市の自治体DX“変革”ハウツー
Kotlin編「並⾏処理・⾮同期処理のアプローチKotlin」(全1記事)
リンクをコピー
記事をブックマーク
狩谷洋平氏(以下、狩谷):よろしくお願いします。「Kotlinの並行処理へのアプローチ」というタイトルで発表します。
最初に簡単な自己紹介です。GO株式会社のIoT開発部で仕事をしている狩谷洋平と申します。2018年4月にサーバーサイドエンジニアとして入社、その後、車載アプリ開発チームへ異動しました。タクシー車両の中で動くAndroidアプリの開発をしています。
狩谷:さて、発表に入ります。最初に「どうして並行処理が必要なの?」という話をしていきます。KotlinといえばAndroidアプリです。
ボタンをタップするとデバイスとの接続状況を確認する機能があるとします。(スライドを示して)スライドの右側にソースコードがあります。button.onClickListenerで、ボタンをタップ時のハンドラを記述できます。
中括弧の中にタップ時の処理が書いてあって、showProgress()で、「診断中」というダイアログを表示します。その後、checkDevices()で実際にデバイスとの接続状況をチェックします。チェックが終わったらhideProgress()でダイアログを消します。そんなソースコードです。
動かすとどうなるでしょうか。注目ポイントとしては、checkDevices()の処理が実際のデバイスと通信をするために時間がかかるというところです。時間がかかるということは、returnされるまで待つことになります。AndroidアプリのようなGUIアプリケーションでは、UIスレッドで時間がかかる処理をすると画面が固まってしまいます。
スライドの吹き出しにあるように、戻るボタンをタップして前画面に戻りたくても、画面が固まってしまっていて、タップしても反応してくれません。これは困ります。そこで、待ち時間に別の処理を実行できる必要があります。つまり、並行処理が必要になります。
狩谷:次に「どうやって並行処理をするの?」という話に移ります。KotlinではCoroutineというものを使って並行処理を実現します。launchというブロックでCoroutineを生成して、Coroutineでブロック内に記述した処理を順次実行します。
(スライド)右側のソースコードは先ほどのものとほぼ同様ですが、checkDevices()とhideProgress()がlaunchブロックで囲まれているところが違います。こうすることで「待ち」が発生したCoroutine以外の処理を実行することができます。checkDevices()によって「待ち」が発生することには変わりませんが、別のCoroutineが動けるので、先ほどと異なり画面が固まっておらず、戻るボタンをタップするとちゃんと反応してくれます。
Coroutineについて、もう少し深掘りしていきます。
狩谷:CoroutineはOSが提供するスレッドではありません。CoroutineはただのKotlinのオブジェクトになります。1つのスレッドで複数のCoroutineに切り替えて実行できます。ただ、Kotlinはシングルスレッドで動いているというわけではなく、複数のスレッドを使って動きます。
Coroutineとスレッドの関係を見ていきましょう。Kotlinランタイムが複数のCoroutineから、次に実行するCoroutineを選んでくれます。さらに、用途別のスレッドのまとまりが用意されています。
(スライド)図の右下にあるようなイメージです。UIスレッドやI/Oで使うスレッドのまとまりなどが用意されています。Kotlinランタイムは複数あるスレッドの中から1つのスレッドを選び出し、Coroutineをスレッドに割り当てて実行してくれます。
プログラマーは用途別のスレッドのまとまりを指定するだけです。スレッドの状態を管理する必要はなく、スレッド一つひとつまで意識しなくてもいいです。ソースコード例のようにlaunch時にパラメーターでDispatchersというものを使って、スレッドのまとまりを指定することができます。
並行処理をするためにCoroutineの数がスレッドより多い場合は、スレッドに対して割り込む必要があります。例えば、UIスレッドは1つしかないので、割り込まないと並行処理ができません。Go言語などでは実行系が強制的に割り込みますが、Kotlinでは中断を明示的に宣言したタイミングでしか割り込みません。
KotlinのCoroutineにおいて、中断とはsuspendというキーワードを付与したメソッドを呼び出したタイミングです。Kotlinではfunというキーワードでメソッドを定義します。funの前にsuspendというキーワードを付加できます。
ソースコードの例がありますが、bというメソッドにsuspendキーワードが付いています。Kotlinコンパイラは、Coroutineで実行したい処理を中断できる処理に変換します。
変換がどういうものかというイメージの話になりますが、launchブロック内に、a、b、cと順次メソッドを呼び出しているソースコードがあるとします。(スライドを示して)この時コンパイラは右側のコードのような処理に変換します。ここの例では、launchブロック全体がinvokeというメソッドになっています。
when式でthis.stateを参照し、その値に応じて処理を分岐します。Coroutineが初めて実行された場合、つまりinvokeが初めて呼ばれた場合、this.stateの値は0になっています。suspend funであるbの呼び出し箇所まで処理が進むと、stateを1にしてからbを呼び出し、returnします。
次にCoroutineが再開された時、つまりもう一度invokeが呼ばれた場合は、stateが1になっているため、分岐によって残りの処理、cメソッドの呼び出しだけが実行されるというわけです。
このように中断と再開をします。Coroutineはsuspend funを呼び出したタイミングで中断します。suspendというキーワードが中断の目印になり、Coroutineを切り替えることができるようになります。
狩谷:次に、KotlinのCoroutineは構造化できるという話をします。Coroutineは親子関係を持つことができます。launchブロックの中で、さらにlaunchすると、親子関係を持ちます。内側で生成されたCoroutineが子どもです。
今まで省略してきましたが、実はlaunchはCoroutineScopeというクラスのメソッドになっています。
ソースコードを見ると、coroutineScope.という記述が増えています。CoroutineScopeオブジェクトと外側のlaunchで作られたCoroutineには、直接関係があることがわかります。
内側のlaunchがどうなっているかというと、thisというキーワードでCoroutineScopeを参照できるようになっています。
Kotlinではthisは省略可能なキーワードなので、外側のlaunchのCoroutineのCoroutineScopeを参照する場合はthisを書いても書かなくても同じ意味になります。
新たに登場したCoroutineScopeですが、CoroutineScopeを使って、親子関係を持つ複数のCoroutineを管理することができます。
管理とは、例えばどれか1つのCoroutineで例外が発生した場合の対応方法があります。
すべてのCoroutineがキャンセルされるようにするというようなことができます。
このように、CoroutineScopeで複数のCoroutineを管理できる仕組みを、Kotlinでは「Structured concurrency」と呼んでいます。Coroutineが実行しっぱなしになることを防ぐことができます。
狩谷:CoroutineScopeの例ですが、Androidアプリ開発では、標準ライブラリから提供されるCoroutineScopeを利用することができます。例えば、表示中の画面に紐づくCoroutineScopeが用意されています。
(スライドを示して)右側のコード例でいうと、viewLifecycleOwner.lifecycleScopeというオブジェクトが、提供されているCoroutineScopeになります。
「画面表示中はナビゲーションを更新し続ける」というような処理があるとします。スクリーンショットでいうと、緑色の線でルート案内が表示がされていて、これを車両の現在地によって書き換えていきます。launchブロックで囲まれている処理はCoroutineで実行されます。whileループでtrueの間はisActiveがナビゲーションを更新し続けてくれるソースコードです。
isActiveでCoroutineがキャンセルされたかどうかを判定します。別画面に切り替わるとCoroutineScopeが終了して、管理下にあるCoroutineがすべてキャンセルされます。
このようにして「画面遷移後に不要になるCoroutineが動きっぱなしになってしまう」ことを簡単に防ぐことができます。
狩谷:まとめです。Kotlinがスレッドに割り当てるCoroutineの切り替えをやってくれます。プログラマーは、Coroutineを程よく制御できる機能を使うことができます。「Coroutineに割り当ててほしいスレッドのまとまりを指定する」とか、「Coroutineを構造化してキャンセルの取り扱い方を決める」などです。
また、スライドでは触れなかったですが、asyncやflowなどといったコードの「書きやすさ/読みやすさ」を上げるAPIが用意されています。
感想になりますが、これらの特徴がAndroidアプリを実装しやすくすることにつながっているな、と感じています。
以上で発表を終わります。ありがとうございました。
司会者:狩谷さん、ありがとうございました。終了時刻が押していますが、狩谷さんへの質問が終わり次第クロージングに移っていくので、みなさま最後までお付き合いいただけるとうれしいです。ということで狩谷さん、1個質問に答えてもらえると。
狩谷:そうですね。「Coroutine内に書いた処理はいつまで継続されるのでしょうか。アクティビティがさらに切り替わったり、アプリが終了されても継続されるのでしょうか」。
先ほどの例にあったisActiveというものをチェックしていると、画面が切り替わるとCoroutineScopeが終了するので、それに引きずられるかたちでCoroutineScopeにぶら下がっているCoroutineたちも終了します。なのでアクティビティが切り替わる、画面が変わるタイミングでCoroutineも止まります。
司会者:ありがとうございます。2つ目もいきますか。
狩谷:「他の言語では見られなかった構文がいろいろありましたが、これらはAndroidアプリ開発が念頭にあって設計された言語だからでしょうか」。
そうですね。構文が多いですね。GoのチャネルみたいなものもKotlinには用意されていたりして、同じ仕組みでだいたい機能として実現することはできるのですが、より簡単に使えるようにするAPIがけっこう用意されています。文法レベルでいうと、最初のsuspendだけであって、あとはライブラリから提供されているAPIがたくさん揃っているという感じのイメージですね。
個人的な印象では、Androidアプリを楽にするために、そういったAPIがどんどん追加されているんじゃないかなと感じています。
司会者:狩谷さん、ありがとうございました。
関連タグ:
2025.02.06
すかいらーく創業者が、社長を辞めて75歳で再起業したわけ “あえて長居させるコーヒー店”の経営に込めるこだわり
PR | 2025.02.07
プロジェクトマネージャーは「無理ゲーを攻略するプレイヤー」 仕事を任せられない管理職のためのマネジメントの秘訣
2025.02.06
落合陽一氏や松尾豊氏の研究は社会に届いているか? ひろゆき氏が語るアカデミアの課題と展望
2025.01.07
1月から始めたい「日記」を書く習慣 ビジネスパーソンにおすすめな3つの理由
2025.02.12
マネージャーは「プレイング3割」が適切 チームの業績を上げるためのマネジメントと業務の比率
2025.02.10
32歳で「すかいらーく」を創業、75歳で「高倉町珈琲」で再起業 「失敗したからすかいらーくができた」横川竟氏流の経営哲学
2025.02.10
A4用紙を持ち歩いて殴り書きでアウトプット コクヨのワークスタイルコンサルタントが語る、2種類のメモ術
2025.02.05
「納得しないと動けない部下」を変える3つのステップとは マネージャーの悩みを解消する会話のテクニック
2025.02.13
“最近の新人は報連相をしない”という、管理職の他責思考 部下に対する「NG指示」から見る、認識のズレを防ぐコツ
2025.02.07
なぜ日本企業のDXは世界の先端企業から3周遅れなのか? 『ゼロ秒思考』著者が指摘する経営者に足りない決断
着想から2か月でローンチ!爆速で新規事業を立ち上げる方法
2025.01.21 - 2025.01.21
新人の報連相スキルはマネージメントで引きあげろ!~管理職の「他責思考」を排除~
2025.01.29 - 2025.01.29
【手放すTALK LIVE#45】人と組織のポテンシャルが継承されるソース原理 ~人と組織のポテンシャルが花開く「ソース原理」とは~
2024.12.09 - 2024.12.09
『これで採用はうまくいく』著者が語る、今こそ採用担当に届けたい「口説く」力のすべて
2024.11.29 - 2024.11.29
【著者来館】『成果を上げるプレイングマネジャーは「これ」をやらない』出版記念イベント!
2025.01.10 - 2025.01.10