Kotlin コルーチンの解決策 (2) - コルーチンのスタートアップ
コルーチンとは何かがわかったので、自分で試してみたいと思うかもしれません。この記事では、コルーチンのいくつかの起動モードの違いについて詳しく紹介します. もちろん、今はソースコード解析の原則に深く入るつもりはありません. コルーチンをうまく使うには、これらのルールを覚えるだけで十分です. .
1. スレッドを初めて学んだときのことを思い出してください
Kotlin に触れる開発者の大半は Java の基盤を持っていると思います.私たちが最初に Thread を学び始めたとき、私たちはこれを行っていたに違いありません:
val thread = object : Thread(){
override fun run() {
super.run()
//do what you want to do.
}
}
thread.start()
誰かがそれを と呼ぶのを忘れたにちがいないしstart
、なぜ私が開いたスレッドが開始されないのだろうか。正直なところ、このスレッドstart
の実際には非常に奇妙ですが、設計者はstop
当時まだ利用可能でしたが、すぐに設計が間違っstop
ている、JDK 1.1 で放棄されたことを理解しています。上記は最も寿命の短い API です。
stop
ミスですから、start
初心者が必ず負けるミスでもありませんか?
はは、ちょっと余談です。今日は主に Kotlin について話します。Kotlin の設計者には素晴らしいアイデアがあり、スレッドに便利な方法を提供しました。
val myThread = thread {
//do what you want
}
このthread
メソッドには、デフォルトでstart
に設定されるtrue
パラメーターがあります。つまり、この方法で作成されたスレッドは、すぐに動作させたくない場合を除き、デフォルトで開始されます。
val myThread = thread(start = false) {
//do what you want
}
//later on ...
myThread.start()
このようにすると、より自然に見えます。インターフェイスの設計では、既定値が要件の 80% を満たすようにする必要があります。
2.コルーチンの開始を見てみましょう
非常に多くのスレッドを述べてきましたが、その理由は、結局のところ、誰もがそれを最もよく知っているからです. コルーチンの API 設計は実際には同じ行にあります. コルーチンを開始する最も簡単な方法を見てみましょう:
GlobalScope.launch {
//do what you want
}
では、このコードはどのように実行されるのでしょうか? 前述したように、コルーチンを開始するには、 context、startup mode、およびコルーチン本体の3 つのものが必要です。Thread.run
この記事では、起動。Kotlin コルーチンでは、起動モードは列挙型です。
public enum class CoroutineStart {
DEFAULT,
LAZY,
@ExperimentalCoroutinesApi
ATOMIC,
@ExperimentalCoroutinesApi
UNDISPATCHED;
}
|
モード
| |
関数
| |
| | — | — |
| |
デフォルト
| |
コルーチン本体をすぐに実行する
| |
| |
アトミック
| |
コルーチンの本体をすぐに実行しますが、実行を開始する前にキャンセルすることはできません
| |
| |
未発送
| |
最初の中断呼び出しまで、現在のスレッドでコルーチンの本体をすぐに実行します
| |
| |
怠惰
| |
必要なときだけ実行
| |
2.1 デフォルト
4 つの起動モードの中で、最も一般的に使用されるのは実際にDEFAULT
はLAZY
。
DEFAULT
ハングリーな中華系スタートアップで、launch
呼び出し後、すぐに待機状態になり、スケジューラーがOKになったら実行開始できます。簡単な例を見てみましょう。
suspend fun main() {
log(1)
val job = GlobalScope.launch {
log(2)
}
log(3)
job.join()
log(4)
}
注: main 関数は Kotlin 1.3 からサスペンドをサポートします。また、メイン関数のパラメータ省略もKotlin 1.3の特徴です。次の例は、特別な指示なしで suspend main 関数で直接実行されます。
このプログラムはデフォルトの起動モードを使用します. スケジューラを指定しなかったので, スケジューラもデフォルトです. JVM では, デフォルトのスケジューラの実装は他の言語のものと似ています. バックグラウンドで処理するスレッドがいくつかあります.非同期タスクなので、上記のプログラムの実行結果は次のようになります。
19:51:08:160 [main] 1
19:51:08:603 [main] 3
19:51:08:606 [DefaultDispatcher-worker-1] 2
19:51:08:624 [main] 4
次の場合もあります。
20:19:06:367 [main] 1
20:19:06:541 [DefaultDispatcher-worker-1] 2
20:19:06:550 [main] 3
20:19:06:551 [main] 4
これは、現在のスレッドとバックグラウンド スレッドの CPU のスケジューリング順序に依存しますが、心配する必要はありません。この例の 2 と 3 の出力順序はそれほど重要ではないことがすぐにわかります。
JVM でのデフォルト スケジューラの実装を既に推測しているかもしれません. はい、スレッド プールが開かれますが、数千のコルーチンをスケジュールするには少数のスレッドで十分であり、各コルーチンには独自の呼び出しスタックがあります. これは、純粋なスケジューラとは根本的に異なります.非同期タスクを実行するためにスレッド プールを開きます。もちろん、Kotlin はクロスプラットフォーム言語なので、上記のコードは Nodejs などの JavaScript 環境でも実行できます。Nodejs では、Kotlin コルーチンのデフォルト スケジューラはスレッド スイッチングを実装しておらず、出力結果は若干異なりますが、JavaScript の実行ロジックに沿っているようです。スケジューラーについては後で詳しく説明します。
2.2 レイジー
LAZY
これは怠惰な起動であり、launch
その後のスケジューリング動作はなく、実行する必要があるまでコルーチン本体は当然実行状態にはなりません。これは実際には少し紛らわしいですが、実行する必要がある場合はどういう意味ですか? つまり、その演算結果が必要な場合、launch
を呼び出した後にJob
インスタンス。この場合、次のことができます。
- を呼び出し
Job.start
、コルーチンのスケジュールされた実行をアクティブにトリガーします - call
Job.join
、コルーチンのスケジュールされた実行を暗黙的にトリガーします
したがって、このいわゆる「必要」は実際には非常に興味深い言い回しでありawait
、Deferred
必要を表現するためにも使用できることが後でわかります。この動作Thread.join
は、呼び出しがjoin
開始されていない場合は効果がない とは異なります。
log(1)
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
log(2)
}
log(3)
job.start()
log(4)
これに基づいて、上記の例の場合、出力は次のようになります。
14:56:28:374 [main] 1
14:56:28:493 [main] 3
14:56:28:511 [main] 4
14:56:28:516 [DefaultDispatcher-worker-1] 2
もちろん、運が良ければ2~4が先行している状況もあるかもしれません。そしてjoin
、
...
log(3)
job.join()
log(4)
コルーチンの実行が完了するまで待機する必要があるため、出力結果は次のようになります。
14:47:45:963 [main] 1
14:47:46:054 [main] 3
14:47:46:069 [DefaultDispatcher-worker-1] 2
14:47:46:090 [main] 4
2.3 アトミック
ATOMIC
キャンセルが関係している場合にのみ意味があります. キャンセル自体も詳細な議論に値するトピックです. ここでは、キャンセル後にコルーチンがキャンセルされる、つまり実行されなくなると単純に考えます. それから、キャンセルを呼び出すタイミングが異なり、コルーチンがスケジュールされる前、スケジュールが開始されたがまだ実行されていない、実行が既に開始されている、実行が完了しているなど、結果も異なります。
itDEFAULT
と、例を見てみましょう。
log(1)
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
log(2)
}
job.cancel()
log(3)
コルーチンを作成した直後にキャンセルしますが、 ATOMIC
modeコルーチンがスケジュールされているため、1、2、3 が出力されますが、2 と 3 の順序はわかりません。
20:42:42:783 [main] 1
20:42:42:879 [main] 3
20:42:42:879 [DefaultDispatcher-worker-1] 2
これに対応して、 DEFAULT
mode、コルーチンが最初にスケジュールされたときに cancel が呼び出された場合、コルーチンは呼び出しなしで直接キャンセルされます. もちろん、コルーチンがその時点でキャンセルされていない可能性もあります.始めたら、普通に起動できます。したがって、前の例をDEFAULT
mode に、2 が出力される場合と出力されない場合があります。
キャンセル呼び出しは、ジョブのステータスを確実にキャンセルに設定しますが、ATOMIC
モード。これを実証するために、例をもう少し複雑にすることができます。
log(1)
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
log(2)
delay(1000)
log(3)
}
job.cancel()
log(4)
job.join()
2 と 3 の間に 1 を追加するとdelay
、delay
コルーチン本体の実行が中断され、後半部分は 1000 ミリ秒後に再度スケジュールされるため、2 が実行されてから 1000 ミリ秒後に 3 が出力されます。ATOMIC
modeについては、すでに開始する必要があることを説明しました. 実際、その実行は、最初のサスペンド ポイントdelay
に遭遇するまで停止しませんが、サスペンド関数です. この時点で、コルーチンは最初のサスペンド関数を案内します.ポイントはdelay
たまたまキャンセルをサポートしているため、次の 3 つは出力されません。
スレッドを使用する場合、スレッド内のタスクの実行を停止したい場合は同様の問題に直面しますが、残念ながら、キャンセルに似たスレッド内の停止インターフェイスは、セキュリティ上の問題により放棄されています。しかし、深く掘り下げていくと、コルーチンのキャンセルは、ある意味でスレッドの中断に似ていることがわかります。
2.4 派遣されていない
これまでの基礎があれば、UNDISPATCHED
わかりやすいです。このモードでは、コルーチンは最初の一時停止ポイントまで現在のスレッドで直接実行を開始します。これは前のものと少し似ていますが、違いはコルーチン本体がスケジューラーなしで実行されることです。ATOMIC
UNDISPATCHED
もちろん、中断ポイントに遭遇した後の実行は、中断ポイント自体のロジックとコンテキスト内のスケジューラーに依存します。
log(1)
val job = GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) {
log(2)
delay(100)
log(3)
}
log(4)
job.join()
log(5)
We still use such an example to understand UNDISPATCHED
the mode . 前の説明によると、コルーチンは開始後すぐに現在のスレッドで実行されるため、1 と 2 はdelay
中断ポイントである同じスレッドで継続的に実行されます。ということで、3は100ms後に再度スケジューリング、このとき4が実行され、join
コルーチンが実行されるのを待つ必要があるので、3が出力されるのを待ってから5を実行します。操作の結果は次のとおりです。
22:00:31:693 [main] 1
22:00:31:782 [main @coroutine#1] 2
22:00:31:800 [main] 4
22:00:31:914 [DefaultDispatcher-worker-1 @coroutine#1] 3
22:00:31:916 [DefaultDispatcher-worker-1 @coroutine#1] 5
角カッコ内はスレッド名ですが、コルーチンを実行するとスレッド名が変更され、存在感のあるものに見えることがわかりました。実行結果には、紛らわしいかもしれない別の詳細があるようです.
join
後続の 5 つのスレッドは 3 と同じです. なぜですか? 前に、サンプルはすべてサスペンド メイン関数で実行されると述べたので、サスペンド メイン関数はコルーチンを直接開始するのに役立ちます。この例のコルーチンはすべてそのサブコルーチンであるため、ここでの 5 のスケジューリングはスケジューリングに依存します最も外側のコルーチンのルール。コルーチンのスケジューリングについては後で説明します。
3. まとめ
この記事では、いくつかの例を使用して、コルーチンのベールを徐々に明らかにします。
QR コードをスキャンして、Kotlin 関連の学習教材を無料で入手してください!
「高度な Kotlin 拡張戦闘 」
第 1 章 Kotlin 入門チュートリアル
● Kotlin の概要
● Kotlin と Java
●Android Studioの上手な使い方
● Kotlin の基本的な型を理解する
● Kotlin の配列に入る
● Kotlin コレクションに入る
● 完全なコード
●基本文法
第 2 章 Kotlin の実用的なピット回避ガイド
● メソッド入力パラメータは定数であり、変更できません
● コンパニオンなし、インスタンス?
● Java のオーバーロード、Kotlin で巧妙な移行を行うには?
● Kotlin での null ジェスチャー
● Kotlin は Java 親クラスのメソッドをオーバーライドします
● Kotlinが「無慈悲」になり、TODOも惜しみなく!
● is と as` の落とし穴
● Kotlin でのプロパティの理解
●またキーワード
● takeIf キーワード
●シングルトンモードの書き方
第3章 プロジェクト戦闘「Kotlin Jetpack戦闘」
● 偉大な神を崇拝するデモから始める
● Kotlin で Gradle スクリプトを書いた経験は?
● Kotlin プログラミングのトリプル レルム
● Kotlinの高階関数
● Kotlinジェネリック
● Kotlin 拡張機能
● Kotlinデリゲート
●コルーチン「未知」のデバッグスキル
● グラフィカル コルーチン: サスペンド