Die Magie der Dispatcher und wie Sie Ihre eigenen Main / Sudo Null IT News erstellen

Ich denke, jetzt gibt es keine Leute mehr, die mit Koroutinen in Kotlin nicht vertraut sind. Zauberwerkzeug, richtig? Noch magischer finde ich es möglich, die Berechnung in einen anderen Thread zu verschieben:

fun main() = runBlocking { println(“Hallo von ${Thread.currentThread().name}”) withContext(Dispatchers.Default) { println(“Hallo von ${Thread.currentThread().name}”) } println (“Willkommen zurück zu ${Thread.currentThread().name}”) } // Ergebnis: // Hallo von main // Hallo von DefaultDispatcher-worker-2 // Willkommen zurück zu main

Nur eine Zeile und wir bekommen Hallo von einem anderen Thread. Wie funktioniert dieser Mechanismus? Es ist eigentlich schmerzlich einfach, wenn Sie sich den CoroutineDispatcher ansehen, werden Sie dort zwei wichtige Methoden bemerken:

public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true öffentlicher abstrakter Spaßdispatch(context: CoroutineContext, block: Runnable)

Der erste ist für die Notwendigkeit verantwortlich, Dispatch aufzurufen, aber der zweite ist etwas interessanter, er ist für die Ausführung des an den Block übergebenen Runnable in einem anderen Thread verantwortlich. Es ist erwähnenswert, dass der Versand eine Blockausführung garantieren muss, sonst kann man sagen Sackgasse und die Coroutine wird die Ausführung niemals fortsetzen, auch sollte die Methode nicht verzögert werden, um den Block auszuführen, falls erforderlich, die Ausführung im aktuellen Thread fortzusetzen, es ist besser, false zurückzugeben von isDispatchNeeded.

Standard?

Wir haben 4 Standard-Dispatcher, auf die über die Dispatchers-Klasse zugegriffen werden kann: Default, Unconfined, Main, IO.

Unconfined: Der einfachste von ihnen ist Unconfined, der den Ausführungsfluss nicht ändert und der Code vom Anfang des Artikels nicht mehr so ​​​​interessant ist:

runBlocking(Dispatchers.Unconfined) { println(“Hallo von ${Thread.currentThread().name}”) withContext(Dispatchers.Default) { println(“Hallo von ${Thread.currentThread().name}”) } println (“Willkommen zurück bei ${Thread.currentThread().name}”) } // Ergebnis: // Hallo von main // Hallo von DefaultDispatcher-worker-2 // Willkommen zurück bei DefaultDispatcher-worker-2)

Dies wird durch isDispatchNeeded erreicht was immer false zurückgibt und erlaubt nicht, dass der Versand aufgerufen wird (Um fair zu sein, Versand kann immer noch von yield() aufgerufen werden, aber das ist eine andere Geschichte).

Standard, IO: Diese beiden werden basierend auf ExecutorCoroutineDispatcher implementiert Mit der Komplexität der Implementierung seines Executors erbt Default den SchedulerCoroutineDispatcher, der Aufgaben ausführt, indem er sie an den CoroutineScheduler sendet. Dies ist ein benutzerdefinierter Executor mit einem Thread-Pool, der der Anzahl der Prozessor-Threads (mindestens 2) für CPU-gebundene Aufgaben entspricht. die erweiterbar sind maxPoolSizegleich dem Systemparameter kotlinx.coroutines.scheduler.max.pool.sizeoder 2097150 maximal, für Sperraufgaben (IO). IO Dispatcher funktioniert über Default und beschränkt sich auf Threads, die dem Systemparameter entsprechen kotlinx.coroutines.io.parallelism oder die Anzahl der Prozessor-Threads (mindestens 64)). CoroutineScheduler muss die blockierende Aufgabe verstehen oder nicht und implementiert dies mit der Methode dispatchWithContext beim SchedulerCoroutineDispatcher, wobei der Aufgabentyp explizit angegeben ist: BlockingContext aus IO und nicht blockierend für alle Aufgaben von Default.

Main: Die Sache, mit der alles begann. Die Coroutinen selbst (Coroutine-Kern) stellen keine Implementierung von MainCoroutineDispatcher bereit, sondern nur einen Mechanismus zum Laden. Das Laden wird von der Klasse MainDispatcherLoader gehandhabt, die ServiceLoader und FastServiceLoader verwendet(Nur verwendet für Android)die explizit versucht, kotlinx.coroutines.android.AndroidDispatcherFactory zu initialisieren. Wenn MainDispatcherLoader findet keine Implementierungen von MainDispatcherFactory oder createDispatcher wird eine Ausnahme auslösen, ein standardmäßiger MissingMainCoroutineDispatcher wird erstellt, Ausnahmen für alles werfen.

Betrachten Sie die Implementierung in Android:

BEI Android MainCoroutineDispatcher auf der Grundlage umgesetzt Handler, ist mit der Initialisierung beschäftigt AndroidDispatcherFactory:

Spaß überschreiben createDispatcher(allFactories: List): MainCoroutineDispatcher { val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException(“The main looper is not available”) return HandlerContext(mainLooper.asHandler(async = true)) } @ VisibleForTesting internal fun Looper.asHandler(async: Boolean): Handler { // Async-Unterstützung wurde in API 16 hinzugefügt. if (!async || Build.VERSION.SDK_INT < 16) { return Handler(this) } if (Build.VERSION .SDK_INT >= 28) { // TODO-Kompilierung gegen API 28, damit diese ohne Reflexion aufgerufen werden kann. val factoryMethod = Handler::class.java.getDeclaredMethod("createAsync", Looper::class.java) return factoryMethod.invoke(null, this) as Handler } val Konstruktor: Konstruktor<Handler> try { constructor = Handler::class.java.getDeclaredConstructor(Looper::class.java, Handler.Callback::class.java, Boolean::class.javaPrimitiveType) } catch (ignoriert: NoSuchMethodException) { // Versteckter Konstruktor fehlt. Greifen Sie auf den nicht asynchronen Konstruktor zurück. return Handler(this) } return constructor.newInstance(this, null, true) }

HandlerContext selbst implementiert MainCoroutineDispatcher Mit Verzögerung und die Ausführung mit Handler::post an den Haupt-Thread senden:

override fun dispatch(context: CoroutineContext, block: Runnable) { if (!handler.post(block)) {cancelOnRejection(context, block) } }

Verzögerung wird auch benötigt, um den Mechanismus der delay()-Funktion, die standardmäßig auf einem dedizierten Thread arbeitet, neu zu definieren Android wird das durchgehen handler.postVerzögert. Auch die Umsetzung können Sie hier einsehen. isDispatchNeededdas ist für MainCoroutineDispatcher.immediate wird den Versand nicht anrufen Vorausgesetzt, Sie befinden sich bereits im Hauptthread.

Eigene Implementierung von MainCoroutineDispatcher: Ich habe mich gefragt, wie man Coroutinen in ein bestehendes Java-Projekt mit einer bereits implementierten Event-Loop ziehen kann. Glücklicherweise habe ich einen Spielserver für Experimente, der vollständig in Java geschrieben ist und in mehreren Threads mit einer Ereignisschleife auf dem Hauptthread läuft. Beginnen wir mit der MainCoroutineDispatcher-Implementierung:

internal class ServerDispatcher( private val invokeImmediately: Boolean ): MainCoroutineDispatcher() { @Volatile private var _immediate = if (invokeImmediately) this else null override val instant = _immediate ?: ServerDispatcher(true).also { _immediate = it } override fun isDispatchNeeded( Kontext: CoroutineContext): Boolean = !invokeImmediately || !Server.getInstance().isPrimaryThread Spaß-Dispatch überschreiben (Kontext: CoroutineContext, Block: Runnable) { Server.getInstance().scheduler.scheduleTask(Block) } Spaß überschreiben limitedParallelism(Parallelism: Int): CoroutineDispatcher { throw UnsupportedOperationException(“limitedParallelism wird nicht unterstützt für ${this::class.qualifiedName}”) } }

Hier unterscheidet sich isDispatchNeeded nicht von dem in Android, der Unterschied liegt im Versand, der das Runnable setzt in die Warteschlange der Aufgaben, die analysiert und in einer Schleife im Haupt-Thread ausgeführt werden. Mal sehen, wie es funktioniert:

val scope = CoroutineScope(ServerDispatcher(invokeImmediately = false) + SupervisorJob()) scope.launch { logger.info(“Erste Nachricht von Coroutine!!”) delay(3000) logger.info(“Nachricht von Coroutine nach 3000 ms Verzögerung bei $ {Thread.currentThread().name} Thread!”) withContext(Dispatchers.IO) { logger.info(“Nachricht aus anderem Kontext: ${Thread.currentThread().name}”) } logger.info(“Du schon wieder auf ${Thread.currentThread().name} Thread!!!”) } // Ergebnis: // 16:04:55 [INFO ] Erste Nachricht von Coroutine!! // 16:04:58 [INFO ] Nachricht von der Coroutine nach 3000 ms Verzögerung im Hauptthread! // 16:04:58 [INFO ] Nachricht aus anderem Kontext: DefaultDispatcher-worker-1 // 16:04:58 [INFO ] Du wieder im Hauptthread!!!

Wir haben dafür gesorgt, dass alles funktioniert, jetzt ist es an der Zeit, den Download durchzuführen. Wir bauen eine Fabrik:

class ServerDispatcherFactory : MainDispatcherFactory { override val loadPriority: Int = Int.MAX_VALUE override fun createDispatcher( allFactories: List, ): MainCoroutineDispatcher = ServerDispatcher(invokeImmediately = false) }

Wir gehen zu den Ressourcen und fügen die Datei kotlinx.coroutines.internal.MainDispatcherFactory ein META-INF/Dienste mit Inhalt:

dev.israpil.coroutines.mygameserver.ServerDispatcherFactory

Wir überprüfen:

val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) scope.launch { logger.info(“Erste Nachricht von Coroutine!!”) delay(3000) logger.info(“Nachricht von Coroutine nach 3000ms Verzögerung bei ${ Thread.currentThread().name} Thread!”) withContext(Dispatchers.IO) { logger.info(“Nachricht aus anderem Kontext: ${Thread.currentThread().name}”) } logger.info(“Du schon wieder ${Thread.currentThread().name} Thread!!!”) } // Ergebnis: // 16:04:55 [INFO ] Erste Nachricht von Coroutine!! // 16:04:58 [INFO ] Nachricht von der Coroutine nach 3000 ms Verzögerung im Haupt-Thread! // 16:04:58 [INFO ] Nachricht aus anderem Kontext: DefaultDispatcher-worker-1 // 16:04:58 [INFO ] Du wieder im Hauptthread!!!

Genießen Sie Coroutinen.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *