コトリンのスレッドとコルーチン

Kotlinのスレッドとコルーチン

1. 前書き

このクイックチュートリアルでは、Kotlinでスレッドを作成して実行します。

後で、Kotlin Coroutinesを優先して、それを完全に回避する方法について説明します。

2. スレッドを作成する

Kotlinでスレッドを作成することは、Javaで作成することに似ています。

Threadクラスを拡張することもできます(ただし、Kotlinは多重継承をサポートしていないためお勧めしません)。

class SimpleThread: Thread() {
    public override fun run() {
        println("${Thread.currentThread()} has run.")
    }
}

または、Runnableインターフェースを実装することもできます。

class SimpleRunnable: Runnable {
    public override fun run() {
        println("${Thread.currentThread()} has run.")
    }
}

また、Javaで行うのと同じ方法で、start()メソッドを呼び出して実行できます。

val thread = SimpleThread()
thread.start()

val threadWithRunnable = Thread(SimpleRunnable())
threadWithRunnable.start()

または、Java 8と同様に、KotlinはSAM Conversionsをサポートしているため、これを利用してラムダを渡すことができます。

val thread = Thread {
    println("${Thread.currentThread()} has run.")
}
thread.start()

2.2. Kotlinthread()関数

もう1つの方法は、Kotlinが提供する関数thread()を検討することです。

fun thread(
  start: Boolean = true,
  isDaemon: Boolean = false,
  contextClassLoader: ClassLoader? = null,
  name: String? = null,
  priority: Int = -1,
  block: () -> Unit
): Thread

この関数を使用すると、次の方法でスレッドをインスタンス化して実行できます。

thread(start = true) {
    println("${Thread.currentThread()} has run.")
}

この関数は5つのパラメーターを受け入れます。

  • start – スレッドをすぐに実行するには

  • isDaemon –スレッドをデーモンスレッドとして作成する

  • contextClassLoader –クラスとリソースのロードに使用するクラスローダー

  • name –スレッドの名前を設定するには

  • priority –スレッドの優先度を設定します

3. Kotlinコルーチン

より多くのスレッドを生成すると、より多くのタスクを同時に実行できると考えるのは魅力的です。 残念ながら、それは必ずしも真実ではありません。

作成するスレッドが多すぎると、状況によっては実際にアプリケーションのパフォーマンスが低下する可能性があります。スレッドは、オブジェクトの割り当ておよびガベージコレクション中にオーバーヘッドを課すオブジェクトです。

これらの問題を克服するために、Kotlin introduced a new way of writing asynchronous, non-blocking code; the Coroutine.

スレッドと同様に、コルーチンは同時に実行し、待機し、相互に通信できますが、コルーチンを作成する方がスレッドよりもはるかに安価です。

3.1. コルーチンコンテキスト

Kotlinがすぐに使用できるコルーチンビルダーを紹介する前に、コルーチンコンテキストについて説明する必要があります。

コルーチンは常に、さまざまな要素のセットであるコンテキストで実行されます。

主な要素は次のとおりです。

  • Job –複数の状態とその完了に至るライフサイクルを備えたキャンセル可能なワークフローをモデル化します

  • Dispatcher –対応するコルーチンが実行に使用する1つまたは複数のスレッドを決定します。 ディスパッチャを使用すると、we can a confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined

次の段階でコルーチンを説明するときに、コンテキストを指定する方法を説明します。

3.2. launch

launch function is a coroutine builder which starts a new coroutine without blocking the current threadは、コルーチンへの参照をJobオブジェクトとして返します。

runBlocking {
    val job = launch(Dispatchers.Default) {
        println("${Thread.currentThread()} has run.")
    }
}

次の2つのオプションパラメータがあります。

  • context –コルーチンが実行されるコンテキスト(定義されていない場合)は、起動元のCoroutineScopeからコンテキストを継承します

  • start –コルーチンの開始オプション。 デフォルトでは、コルーチンはすぐに実行がスケジュールされます

上記のコードは、GlobalScope.で起動するDispatchers.Default を使用しているため、スレッドの共有バックグラウンドプールで実行されることに注意してください。

または、同じコーディネーターを使用するGlobalScope.launch を使用することもできます。

val job = GlobalScope.launch {
    println("${Thread.currentThread()} has run.")
}

Dispatchers.DefaultまたはGlobalScope.launchを使用すると、トップレベルのコルーチンが作成されます。 軽量ですが、実行中にメモリリソースを消費します。

スレッドで通常行うように(スレッドは常にグローバルです)、GlobalScopeでコルーチンを起動する代わりに、実行している操作の特定のスコープでコルーチンを起動できます。

runBlocking {
    val job = launch {
        println("${Thread.currentThread()} has run.")
    }
}

この場合、コンテキストを指定せずに、runBlocking coroutineビルダー(後で説明します)内で新しいコルーチンを開始します。 したがって、コルーチンはrunBlockingのコンテキストを継承します。

3.3. async

コルーチンを作成するためにKotlinが提供するもう1つの関数は、asyncです。

async関数は新しいコルーチンを作成し、Deferred<T>:のインスタンスとして将来の結果を返します

val deferred = async {
    [email protected] "${Thread.currentThread()} has run."
}

deferredは、最初は不明な結果のプロキシとして機能するオブジェクトを表す、非ブロッキングのキャンセル可能な未来です。

launch,のように、コルーチンを実行するコンテキストと開始オプションを指定できます。

val deferred = async(Dispatchers.Unconfined, CoroutineStart.LAZY) {
    println("${Thread.currentThread()} has run.")
}

この場合、DispatchersUnconfinedを使用してコルーチンを起動しました。これにより、呼び出し元のスレッドでコルーチンが開始されますが、最初の一時停止ポイントまでのみです。

コルーチンがCPU時間を消費せず、共有データを更新しない場合は、DispatchersUnconfinedが適していることに注意してください。

In addition, Kotlin provides Dispatchers.IO that uses a shared pool of on-demand created threads

val deferred = async(Dispatchers.IO) {
    println("${Thread.currentThread()} has run.")
}

集中的なI / O操作を行う必要がある場合は、Dispatchers.IOをお勧めします。

3.4. runBlocking

runBlockingについては以前に説明しましたが、次に詳しく説明します。

runBlockingは、新しいコルーチンを実行し、完了するまで現在のスレッドをブロックする関数です。

前のスニペットの例として、コルーチンを起動しましたが、結果を待ちませんでした。

結果を待つには、await()のsuspendメソッドを呼び出す必要があります。

// async code goes here

runBlocking {
    val result = deferred.await()
    println(result)
}

await() isはいわゆるサスペンド関数です。 Suspend functions are only allowed to be called from a coroutine or another suspend function.このため、runBlocking呼び出しで囲みました。

main関数とテストでrunBlockingを使用して、ブロックコードをサスペンドスタイルで記述された他のコードにリンクできるようにします。

他のコルーチンビルダーで行ったのと同様の方法で、実行コンテキストを設定できます。

runBlocking(newSingleThreadContext("dedicatedThread")) {
    val result = deferred.await()
    println(result)
}

コルーチンを実行できる新しいスレッドを作成できることに注意してください。 ただし、専用スレッドは高価なリソースです。 そして、不要になったら、リリースするか、アプリケーション全体で再利用する必要があります。

4. 結論

このチュートリアルでは、スレッドを作成して非同期の非ブロッキングコードを実行する方法を学びました。

スレッドの代わりとして、コルーチンを使用するためのKotlinのアプローチがいかにシンプルでエレガントであるかも見てきました。

いつものように、このチュートリアルに示されているすべてのコードサンプルはover on Githubで利用できます。