Threads vs. Coroutines in Kotlin

Themen gegen Coroutinen in Kotlin

1. Einführung

In diesem kurzen Tutorial werden wir Threads in Kotlin erstellen und ausführen.

Später werden wir diskutieren, wie dies insgesamt zugunsten vonKotlin Coroutines vermieden werden kann.

2. Threads erstellen

Das Erstellen eines Threads in Kotlin ähnelt dem Erstellen in Java.

Wir könnten entweder dieThread-Klasse erweitern (obwohl dies nicht empfohlen wird, da Kotlin keine Mehrfachvererbung unterstützt):

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

Oder wir können dieRunnable-Schnittstelle implementieren:

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

Und genauso wie in Java können wir es ausführen, indem wir diestart()-Methode aufrufen:

val thread = SimpleThread()
thread.start()

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

Alternativ unterstützt Kotlin wie Java 8SAM Conversions, daher können wir es nutzen und ein Lambda übergeben:

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

2.2. Kotlinthread() Funktion

Eine andere Möglichkeit besteht darin, die von Kotlin bereitgestellte Funktionthread() zu berücksichtigen:

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

Mit dieser Funktion kann ein Thread einfach wie folgt instanziiert und ausgeführt werden:

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

Die Funktion akzeptiert fünf Parameter:

  • start – Um den Thread sofort auszuführen

  • isDaemon - Zum Erstellen des Threads als Daemon-Thread

  • contextClassLoader - Ein Klassenladeprogramm zum Laden von Klassen und Ressourcen

  • name – Zum Festlegen des Namens des Threads

  • priority - Zum Festlegen der Priorität des Threads

3. Kotlin Coroutines

Es ist verlockend zu glauben, dass wir durch das Laichen weiterer Threads mehr Aufgaben gleichzeitig ausführen können. Leider stimmt das nicht immer.

Wenn zu viele Threads erstellt werden, kann die Leistung einer Anwendung in bestimmten Situationen sogar beeinträchtigt werden. Threads sind Objekte, die während der Objektzuweisung und der Garbage Collection einen Overhead verursachen.

Um diese Probleme zu lösen, müssenKotlin introduced a new way of writing asynchronous, non-blocking code; the Coroutine.

Ähnlich wie Threads können Coroutinen gleichzeitig ausgeführt werden, auf sie warten und miteinander kommunizieren, mit dem Unterschied, dass das Erstellen dieser Threads weitaus billiger ist als Threads.

3.1. Coroutine-Kontext

Bevor wir die Coroutine-Builder vorstellen, die Kotlin standardmäßig bereitstellt, müssen wir den Coroutine-Kontext erörtern.

Coroutinen werden immer in einem Kontext ausgeführt, der aus verschiedenen Elementen besteht.

Die Hauptelemente sind:

  • Job - modelliert einen stornierbaren Workflow mit mehreren Zuständen und einem Lebenszyklus, der in seiner Fertigstellung gipfelt

  • Dispatcher - bestimmt, welchen Thread oder welche Threads die entsprechende Coroutine für ihre Ausführung verwendet. Mit dem Dispatcherwe can a confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined

Wir werden sehen, wie der Kontext angegeben wird, während wir die Coroutinen in den nächsten Schritten beschreiben.

3.2. launch

launch function is a coroutine builder which starts a new coroutine without blocking the current thread und gibt einen Verweis auf die Coroutine alsJob-Objekt zurück:

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

Es gibt zwei optionale Parameter:

  • context - Der Kontext, in dem die Coroutine ausgeführt wird. Wenn sie nicht definiert ist, erbt sie den Kontext vonCoroutineScope, von dem aus sie gestartet wird

  • start - Die Startoptionen für die Coroutine. Standardmäßig ist die Ausführung der Coroutine sofort geplant

Beachten Sie, dass der obige Code in einem gemeinsam genutzten Hintergrundpool von Threads ausgeführt wird, da wirDispatchers.Default verwendet haben, das ihn inGlobalScope. startet

Alternativ können wirGlobalScope.launch verwenden, das denselben Dispatcher verwendet:

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

Wenn wirDispatchers.Default oderGlobalScope.launch verwenden, erstellen wir eine Coroutine der obersten Ebene. Obwohl es ein geringes Gewicht hat, verbraucht es während der Ausführung dennoch einige Speicherressourcen.

Anstatt Coroutinen inGlobalScope zu starten, wie wir es normalerweise mit Threads tun (Threads sind immer global), können wir Coroutinen in dem spezifischen Umfang der Operation starten, die wir ausführen:

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

In diesem Fall starten wir eine neue Coroutine imrunBlocking coroutine Builder (den wir später beschreiben werden), ohne den Kontext anzugeben. Somit erbt die Coroutine den Kontext vonrunBlocking.

3.3. async

Eine weitere Funktion, die Kotlin zum Erstellen einer Coroutine bereitstellt, istasync.

Die Funktionasync erstellt eine neue Coroutine und gibt ein zukünftiges Ergebnis als Instanz vonDeferred<T>: zurück

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

deferred ist eine nicht blockierende stornierbare Zukunft, die ein Objekt beschreibt, das als Proxy für ein zunächst unbekanntes Ergebnis fungiert.

Wielaunch, können wir einen Kontext angeben, in dem die Coroutine ausgeführt werden soll, sowie eine Startoption:

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

In diesem Fall haben wir die Coroutine mitDispatchers.Unconfined gestartet, wodurch Coroutinen im Aufrufer-Thread gestartet werden, jedoch nur bis zum ersten Suspendierungspunkt.

Beachten Sie, dassDispatchers.Unconfined gut passt, wenn eine Coroutine weder CPU-Zeit verbraucht noch gemeinsam genutzte Daten aktualisiert.

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.")
}

Dispatchers.IO wird empfohlen, wenn intensive E / A-Vorgänge ausgeführt werden müssen.

3.4. runBlocking

Wir haben unsrunBlocking früher angesehen, aber jetzt wollen wir genauer darauf eingehen.

runBlocking ist eine Funktion, die eine neue Coroutine ausführt und den aktuellen Thread bis zu seiner Fertigstellung blockiert.

Als Beispiel im vorherigen Snippet haben wir die Coroutine gestartet, aber nie auf das Ergebnis gewartet.

Um auf das Ergebnis zu warten, müssen wir die Suspend-Methodeawait()aufrufen:

// async code goes here

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

await() ist eine sogenannte Suspend-Funktion. Suspend functions are only allowed to be called from a coroutine or another suspend function. Aus diesem Grund haben wir es in einenrunBlocking-Aufruf eingeschlossen.

Wir verwendenrunBlocking inmain Funktionen und in Tests, damit wir Blockierungscode mit anderen verknüpfen können, die im Suspending-Stil geschrieben wurden.

Auf ähnliche Weise wie in anderen Coroutine-Buildern können wir den Ausführungskontext festlegen:

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

Beachten Sie, dass wir einen neuen Thread erstellen können, in dem wir die Coroutine ausführen können. Ein dedizierter Thread ist jedoch eine teure Ressource. Und wenn es nicht mehr benötigt wird, sollten wir es freigeben oder besser für die gesamte Anwendung wiederverwenden.

4. Fazit

In diesem Tutorial haben wir gelernt, wie man asynchronen, nicht blockierenden Code durch Erstellen eines Threads ausführt.

Als Alternative zum Thread haben wir auch gesehen, wie einfach und elegant Kotlins Ansatz zur Verwendung von Coroutinen ist.

Wie üblich sind alle in diesem Lernprogramm gezeigten Codebeispieleover on Github verfügbar.