1. 序章

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

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

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変換をサポートしているため、これを利用してラムダを渡すことができます。

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

2.2. Kotlin thread()関数

もう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は非同期の非ブロッキングコードを記述する新しい方法を導入しました。 コルーチン。

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

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

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

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

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

  • Job –複数の状態とその完了に至るライフサイクルを備えたキャンセル可能なワークフローをモデル化します
  • Dispatcher –対応するコルーチンが実行に使用する1つまたは複数のスレッドを決定します。 ディスパッチャを使用すると、コルーチンの実行を特定のスレッドに制限したり、スレッドプールにディスパッチしたり、制限なしで実行したりできます

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

3.2. 起動

launch関数は、現在のスレッドをブロックせずに新しいコルーチンを開始し、Jobオブジェクトとしてコルーチンへの参照を返すコルーチンビルダーです。

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

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

  • context –コルーチンが実行されるコンテキスト(定義されていない場合)は、起動元のCoroutineScopeからコンテキストを継承します
  • start –コルーチンの開始オプション。 デフォルトでは、コルーチンはすぐに実行されるようにスケジュールされています

上記のコードは、[X193X]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 コルーチンビルダー(後で説明します)内で新しいコルーチンを開始します。 したがって、コルーチンはrunBlockingのコンテキストを継承します。

3.3. 非同期

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

The 非同期関数は新しいコルーチンを作成し、のインスタンスとして将来の結果を返します延期

val deferred = async {
    return@async "${Thread.currentThread()} has run."
}

deferred は、ブロックされないキャンセル可能なfutureであり、最初は不明な結果のプロキシとして機能するオブジェクトを記述します。

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

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

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

ディスパッチャーUnconfinedは、コルーチンがCPU時間を消費せず、共有データを更新しない場合に適しています。

さらに、Kotlinはオンデマンドで作成されたスレッドの共有プールを使用するDispatchers.IOを提供します

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()はいわゆるサスペンド関数です。 サスペンド関数は、コルーチンまたは別のサスペンド関数からのみ呼び出すことができます。このため、runBlocking呼び出しで囲みました。

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

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

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

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

4. 結論

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

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

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