{"id":145835,"date":"2021-05-21T15:38:00","date_gmt":"2021-05-21T14:38:00","guid":{"rendered":"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-coroutines-1-5-0-released\/"},"modified":"2021-05-26T12:08:09","modified_gmt":"2021-05-26T11:08:09","slug":"kotlin-coroutines-1-5-0-released","status":"publish","type":"kotlin","link":"https:\/\/blog.jetbrains.com\/de\/kotlin\/2021\/05\/kotlin-coroutines-1-5-0-released\/","title":{"rendered":"Kotlin Coroutines 1.5: GlobalScope als \u201eheikel\u201c markiert, Channels-API optimiert \u2013 und vieles mehr"},"content":{"rendered":"\n<p><em>Mitautorin: <a href=\"https:\/\/blog.jetbrains.com\/author\/svetlana-isakova\/\" class=\"ek-link\">Svetlana Isakova<\/a><\/em><\/p>\n\n\n\n<p>Kotlin Coroutines 1.5.0 ist da! Dies sind die \u00c4nderungen in der neuen Version:<\/p>\n\n\n\n<ul><li><a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-coroutines-1-5-0-released\/#globalscope\" class=\"ek-link\">GlobalScope ist jetzt als \u201edelicate API\u201c (heikle API) markiert.<\/a> GlobalScope ist eine API f\u00fcr fortgeschrittene Anwendungsf\u00e4lle, die leicht missbraucht werden kann. Der Compiler warnt Sie nun vor einem m\u00f6glichen falschen Gebrauch und erfordert ein explizites Opt-in f\u00fcr die Nutzung dieser Klasse in Ihrem Programm.<\/li><\/ul>\n\n\n\n<ul><li><a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-coroutines-1-5-0-released\/#junit\" class=\"ek-link\">Erweiterungen f\u00fcr JUnit.<\/a> CoroutinesTimeout ist jetzt f\u00fcr JUnit 5 verf\u00fcgbar.<\/li><\/ul>\n\n\n\n<ul><li><a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-coroutines-1-5-0-released\/#channels\" class=\"ek-link\">Optimierte Channels-API.<\/a> Zusammen mit einem neuen Namensschema f\u00fcr die Bibliotheksfunktionen wurden die nicht suspendierenden Funktionen <code>trySend<\/code> und <code>tryReceive<\/code> als \u00fcberlegene Alternativen f\u00fcr <code>offer<\/code> und <code>poll<\/code> eingef\u00fchrt.<\/li><\/ul>\n\n\n\n<ul><li><a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-coroutines-1-5-0-released\/#reactive\" class=\"ek-link\">Stabiler Status f\u00fcr Reactive-Integrationen.<\/a> Wir haben weitere Funktionen hinzugef\u00fcgt, um Reactive-Streams-Typen in Kotlin Flow und umgekehrt zu konvertieren, und viele vorhandene Funktionen sowie die ReactiveContext-API wurden als stabil markiert.<\/li><\/ul>\n\n\n\n<p>In diesem Blogbeitrag finden Sie auch Empfehlungen f\u00fcr die Migration auf die neue Version.<\/p>\n\n\n\n<span id=\"more-142622\"><\/span>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Kotlin Coroutines 1.5: GlobalScope als \u201eheikel\u201c markiert, Channels-API optimiert \u2013 und vieles mehr\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/EVLnWOcR0is?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p align=\"center\"><a class=\"ek-link jb-download-button\" title=\"Coroutines 1.5.0 verwenden\" href=\"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-coroutines-1-5-0-released\/#use-coroutines-1-5-0\">Mit Coroutines 1.5.0 loslegen<\/a><\/p>\n\n\n\n<h1 id=\"globalscope\">GlobalScope als \u201eheikle\u201c API markiert <\/h1>\n\n\n\n<p>Die Klasse <code>GlobalScope<\/code> ist jetzt mit der Annotation <code>@DelicateCoroutinesApi<\/code> markiert. In Zukunft ist f\u00fcr jede Verwendung von <code>GlobalScope<\/code> ein explizites Opt-in mit <code>@OptIn(DelicateCoroutinesApi::class)<\/code> erforderlich.<\/p>\n\n\n\n<p>Die Verwendung von <code>GlobalScope<\/code> wird zwar in den meisten F\u00e4llen nicht empfohlen, die <a href=\"https:\/\/kotlinlang.org\/docs\/coroutines-basics.html#your-first-coroutine\" target=\"_blank\" rel=\"noopener\">offizielle Dokumentation<\/a> verwendet diese \u201eheikle\u201c API jedoch weiterhin, um die Konzepte vorzustellen.<\/p>\n\n\n\n<p>Ein globaler <code>CoroutineScope<\/code> ist nicht an einen Job gebunden. Der globale Scope wird verwendet, um Top-Level-Coroutinen zu starten, die w\u00e4hrend der gesamten Anwendungslebensdauer ausgef\u00fchrt werden und nicht vorzeitig beendet werden. Aktive Coroutinen, die im <code>GlobalScope<\/code> gestartet wurden, halten den Prozess nicht am Laufen. Sie sind wie Daemon-Threads.<\/p>\n\n\n\n<p>Diese API ist heikel in der Verwendung, da es durch <code>GlobalScope<\/code> leicht zu versehentlichen Ressourcen- oder Speicherlecks kommen kann. Eine im <code>GlobalScope<\/code> gestartete Coroutine unterliegt nicht dem Prinzip der strukturierten Parallelit\u00e4t. Wenn sie also aufgrund eines Problems (z. B. langsames Netzwerk) h\u00e4ngt oder eine Verz\u00f6gerung erf\u00e4hrt, l\u00e4uft sie weiter und verbraucht Ressourcen. Betrachten wir beispielsweise den folgenden Code:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nfun loadConfiguration() {\n    GlobalScope.launch {\n        val config = fetchConfigFromServer() \/\/ network request\n        updateConfiguration(config)\n    }\n}\n<\/pre>\n\n\n\n<p>Ein Aufruf von <code>loadConfiguration<\/code> erstellt eine Coroutine im <code>GlobalScope<\/code>, die im Hintergrund l\u00e4uft, ohne dass es eine M\u00f6glichkeit gibt, die Ausf\u00fchrung abzubrechen oder auf ihre Beendigung zu warten. Wenn das Netzwerk langsam ist, verbraucht das Warten im Hintergrund Ressourcen. Durch wiederholte Aufrufe von <code>loadConfiguration<\/code> werden immer mehr Ressourcen in Beschlag genommen.<\/p>\n\n\n\n<h2>M\u00f6gliche Alternativen<\/h2>\n\n\n\n<p>In vielen F\u00e4llen sollte die Verwendung von <code>GlobalScope<\/code> vermieden werden. Stattdessen sollte der entsprechende Vorgang mit <code>suspend<\/code> gekennzeichnet werden. Zum Beispiel:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nsuspend fun loadConfiguration() {\n    val config = fetchConfigFromServer() \/\/ network request\n    updateConfiguration(config)\n}\n<\/pre>\n\n\n\n<p>In F\u00e4llen, in denen <code>GlobalScope.launch<\/code> verwendet wird, um mehrere parallele Vorg\u00e4nge zu starten, sollten diese stattdessen mit <code>coroutineScope<\/code> gruppiert werden:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\n\/\/ concurrently load configuration and data\nsuspend fun loadConfigurationAndData() {\n    coroutineScope {\n        launch { loadConfiguration() }\n        launch { loadData() }\n    }\n}\n<\/pre>\n\n\n\n<p>Wenn in Top-Level-Code ein parallel ausgef\u00fchrter Vorgang aus einem nicht suspendierenden Kontext gestartet wird, sollte eine entsprechend eingeschr\u00e4nkte Instanz von <code>CoroutineScope<\/code> anstelle von <code>GlobalScope<\/code> verwendet werden.<\/p>\n\n\n\n<h2>Sinnvolle Anwendungsf\u00e4lle<\/h2>\n\n\n\n<p>In einigen F\u00e4llen ist die Verwendung von <code>GlobalScope<\/code> legitim und sicher \u2013 zum Beispiel bei Top-Level-Hintergrundprozessen, die w\u00e4hrend der gesamten Lebensdauer der Anwendung aktiv bleiben sollen. Aus den angef\u00fchrten Gr\u00fcnden erfordert jede Verwendung von <code>GlobalScope<\/code> ein explizites Opt-in mit <code>@OptIn(DelicateCoroutinesApi::class)<\/code>:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\n\/\/ A global coroutine to log statistics every second, \n\/\/ must be always active\n@OptIn(DelicateCoroutinesApi::class)\nval globalScopeReporter = GlobalScope.launch {\n    while (true) {\n        delay(1000)\n        logStatistics()\n    }\n}\n<\/pre>\n\n\n\n<p>Wir empfehlen, alle Verwendungen von <code>GlobalScope<\/code> sorgf\u00e4ltig zu pr\u00fcfen und nur diejenigen mit der Annotation zu versehen, die in die Kategorie \u201esinnvolle Anwendungsf\u00e4lle\u201c fallen. Andere Verwendungen k\u00f6nnen sehr leicht zu Fehlern in Ihrem Code f\u00fchren \u2013 ersetzen Sie diese Verwendungen von <code>GlobalScope<\/code> daher wie oben beschrieben.<\/p>\n\n\n\n<h1 id=\"junit\">Erweiterungen f\u00fcr JUnit 5<\/h1>\n\n\n\n<p>Die Annotation <code>CoroutinesTimeout<\/code> erm\u00f6glicht es, Tests in einem separaten Thread auszuf\u00fchren. Nach einem angegebenen Zeitlimit wird der Thread mit einem Testfehler abgebrochen. Bisher war <code>CoroutinesTimeout<\/code> f\u00fcr JUnit 4 verf\u00fcgbar. In dieser Version haben wir eine Integration f\u00fcr JUnit 5 hinzugef\u00fcgt.<\/p>\n\n\n\n<p>Um die neue Annotation zu verwenden, f\u00fcgen Sie Ihrem Projekt die folgende Abh\u00e4ngigkeit hinzu:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\ndependencies {\n  \u2026\n  testImplementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutinesVersion&quot;)\n}\n<\/pre>\n\n\n\n<p>Hier ist ein einfaches Beispiel f\u00fcr die Verwendung von <code>CoroutinesTimeout<\/code> in Ihren Tests:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nimport kotlinx.coroutines.debug.junit5.CoroutinesTimeout\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\n\n@CoroutinesTimeout(100)\nclass CoroutinesTimeoutSimpleTest {\n\n     @CoroutinesTimeout(300)\n     @Test\n     fun firstTest() {\n         runBlocking {\n             delay(200)  \/\/ succeeds\n         }\n     }\n\n     @Test\n     fun secondTest() {\n         runBlocking {\n             delay(200)  \/\/ fails\n         }\n     }\n }\n<\/pre>\n\n\n\n<p>In diesem Beispiel wird das Zeitlimit f\u00fcr Coroutinen auf Klassenebene f\u00fcr <code>firstTest<\/code> definiert. Der annotierte Test hat kein Zeitlimit, da die Annotation der Funktion Vorrang vor der Annotation auf Klassenebene hat. <code>secondTest<\/code> wiederum verwendet die Annotation auf Klassenebene, sodass ein Zeitlimit gilt.<\/p>\n\n\n\n<p>Die Annotation wird wie folgt deklariert:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\npackage kotlinx.coroutines.debug.junit5\n\npublic annotation class CoroutinesTimeout(\n    val testTimeoutMs: Long,\n    val cancelOnTimeout: Boolean = false\n)\n<\/pre>\n\n\n\n<p>Der erste Parameter, <code>testTimeoutMs<\/code>, gibt das Zeitlimit in Millisekunden an. Der zweite Parameter, <code>cancelOnTimeout<\/code>, legt fest, ob nach Ablauf des Zeitlimits alle laufenden Coroutinen abgebrochen werden sollen. Wenn dieser den Wert <code>true<\/code> hat, werden alle Coroutinen automatisch abgebrochen.<\/p>\n\n\n\n<p>Bei Verwendung der Annotation <code>CoroutinesTimeout<\/code> wird automatisch der <a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2020\/07\/kotlin-1-4-rc-debugging-coroutines\/\">Coroutinen-Debugger<\/a> aktiviert, und nach Ablauf des Zeitlimits wird ein Dump aller Coroutinen gespeichert. Der Dump enth\u00e4lt die Stacktraces zur Erstellung der Coroutinen. Wenn zur Beschleunigung der Tests das Speichern der Erstellungs-Stacktraces deaktiviert werden soll, empfiehlt sich die Verwendung von <code><a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/blob\/master\/kotlinx-coroutines-debug\/src\/junit\/junit5\/CoroutinesTimeoutExtension.kt\" class=\"ek-link\" target=\"_blank\" rel=\"noopener\">CoroutinesTimeoutExtension<\/a><\/code>, die diese M\u00f6glichkeit bietet.<\/p>\n\n\n\n<p>Vielen Dank an <a href=\"https:\/\/github.com\/asarkar\" target=\"_blank\" rel=\"noopener\">Abhijit Sarkar<\/a>, der einen n\u00fctzlichen PoC f\u00fcr <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/issues\/2197\" class=\"ek-link\" target=\"_blank\" rel=\"noopener\">CoroutinesTimeout f\u00fcr JUnit 5<\/a> erstellt hat. Die Idee wurde zur neuen Annotation <code>CoroutinesTimeout<\/code> weiterentwickelt, die wir in Version 1.5 eingef\u00fchrt haben.<\/p>\n\n\n\n<h1 id=\"channels\">Optimierte Channels-API<\/h1>\n\n\n\n<p>Channels sind wichtige Grundbausteine der Kommunikation, mit denen Sie Daten zwischen Coroutinen und Callbacks \u00fcbertragen k\u00f6nnen. In dieser Version haben wir die Channels-API ein wenig \u00fcberarbeitet und die Funktionen <code>offer<\/code> und <code>poll<\/code>, die oft f\u00fcr Verwirrung sorgten, durch bessere Alternativen ersetzt. Dabei haben wir gleich eine neue, einheitliche Namensgebung f\u00fcr suspendierende und nicht suspendierende Methoden entwickelt.<\/p>\n\n\n\n<h2>Das neue Namensschema<\/h2>\n\n\n\n<p>Wir haben versucht, ein einheitliches Namensschema zu finden, das auch in anderen Bibliotheken oder in der Coroutines-API verwendet werden kann. Dabei sollte der Name der Funktion einen Hinweis auf ihr Verhalten geben. Am Ende haben wir uns f\u00fcr Folgendes entschieden:<\/p>\n\n\n\n<ul><li>Die regul\u00e4ren, suspendierenden Methoden behalten ihre Namen unver\u00e4ndert bei, z. B. <code>send<\/code>, <code>receive<\/code>.<\/li><li>Ihren nicht suspendierenden Gegenst\u00fccken mit Fehlerkapselung wird einheitlich das Pr\u00e4fix \u201etry\u201c vorangestellt: <code>trySend<\/code> und <code>tryReceive<\/code> anstelle der alten Namen <code>offer<\/code> und <code>poll<\/code>.<\/li><li>Neue suspendierende Methoden mit Fehlerkapselung werden das Suffix \u201eCatching\u201c erhalten.<\/li><\/ul>\n\n\n\n<p>Wenden wir uns nun den Details dieser neuen Methoden zu.<\/p>\n\n\n\n<h2><code>Try<\/code>-Funktionen: nicht suspendierende Gegenst\u00fccke zu <code>send<\/code> und <code>receive<\/code><\/h2>\n\n\n\n<p>Eine Coroutine kann Informationen an einen Channel senden, w\u00e4hrend eine andere Coroutine diese Informationen vom Channel abruft. Sowohl <code>send<\/code> als auch <code>receive<\/code> sind suspendierende Funktionen. <code>send<\/code> suspendiert ihre Coroutine, wenn der Channel voll ist und keine neuen Elemente aufnehmen kann, w\u00e4hrend <code>receive<\/code> ihre Coroutine suspendiert, wenn der Channel keine Elemente enth\u00e4lt:<\/p>\n\n\n\n<pre class=\"kotlin-code\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\n\nfun main() = runBlocking&lt;Unit&gt; {\n    val channel = Channel&lt;String&gt;()\n    launch {\n        \/\/ Suspends until the element can be sent\n        println(&quot;Sending...&quot;)\n        channel.send(&quot;Element&quot;)\n     }\n     \/\/ Suspends until the element can be received\n     println(&quot;Receiving...&quot;)\n     println(channel.receive())\n}\n<\/pre>\n\n\n\n<p>Zur Verwendung in synchronem Code haben diese Funktionen nicht suspendierende Gegenst\u00fccke: <code>offer<\/code> und <code>poll<\/code>, die nun von <code>trySend<\/code> und <code>tryReceive<\/code> abgel\u00f6st wurden und als veraltet gekennzeichnet sind. Sehen wir uns die Gr\u00fcnde f\u00fcr diese \u00c4nderung an.<\/p>\n\n\n\n<p><code>offer<\/code> und <code>poll<\/code> haben dieselbe Funktion wie <code>send<\/code> und <code>receive<\/code>, nur ohne Suspendierung. Das klingt einfach und alles funktioniert einwandfrei, solange beim Senden und Empfangen kein Problem auftritt. Aber was passiert bei einem Fehler? <code>send<\/code> und <code>receive<\/code> w\u00fcrden mit einer Suspendierung darauf warten, dass sie ihren Job erledigen k\u00f6nnen. <code>offer<\/code> und <code>poll<\/code> gaben einfach <code>false<\/code> bzw. <code>null<\/code> zur\u00fcck, wenn das Element nicht hinzugef\u00fcgt werden konnte, weil der Channel voll war, bzw. wenn kein Element abgerufen werden konnte, weil der Channel leer war. Beide l\u00f6sten eine Ausnahme aus, wenn der Channel geschlossen war, und dieses eine Detail erwies sich als verwirrend.<\/p>\n\n\n\n<pre class=\"kotlin-code\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\n\nfun main() = runBlocking&lt;Unit&gt; {\n    val channel = Channel&lt;String&gt;()\n    launch {\n        println(&quot;Sending...&quot;)\n        \/\/ Doesn't suspend\n        \/\/ Returns 'false' if the channel is full\n        \/\/ Or throws an exception if it's closed\n        channel.offer(&quot;Element&quot;)\n    }\n    println(&quot;Receiving...&quot;)\n    \/\/ Doesn't suspend\n    \/\/ Returns 'null' if the channel is empty\n    println(channel.poll())\n\/\/  channel.close()\n}\n<\/pre>\n\n\n\n<p>In diesem Beispiel wird <code>poll<\/code> aufgerufen, bevor ein Element hinzugef\u00fcgt wird, und daher wird sofort <code>null<\/code> zur\u00fcckgegeben. Der direkte Aufruf in diesem Fall ist nat\u00fcrlich eine Vereinfachung \u2013 im Normalfall w\u00fcrde man regelm\u00e4\u00dfig Elemente abfragen. Der Aufruf von <code>offer<\/code> ist auch nicht erfolgreich, da unser Channel ein Rendezvous-Channel mit einer Pufferkapazit\u00e4t von null ist. Nur weil die Funktionen in der falschen Reihenfolge aufgerufen wurden, gibt <code>offer<\/code> den Wert <code>false<\/code> und <code>poll<\/code> den Wert <code>null<\/code> zur\u00fcck.<\/p>\n\n\n\n<p>Wenn Sie im obigen Beispiel die Kommentierung von <code>channel.close()<\/code> aufheben, wird eine Ausnahme ausgel\u00f6st. In diesem Fall gibt <code>poll<\/code> wie zuvor <code>false<\/code> zur\u00fcck. Aber dann versucht <code>offer<\/code> einem bereits geschlossenen Channel ein Element hinzuzuf\u00fcgen, und dies l\u00f6st eine Ausnahme aus. Wir haben viele Beschwerden \u00fcber die Fehleranf\u00e4lligkeit dieses Verhaltens erhalten. Man vergisst leicht, diese Ausnahme abzufangen, obwohl man sie eigentlich ignorieren oder anderweitig behandeln wollte, und das Programm st\u00fcrzt ab.<\/p>\n\n\n\n<p>Die neuen Funktionen <code>trySend<\/code> und <code>tryReceive<\/code> beheben dieses Problem und geben ein detaillierteres Ergebnis zur\u00fcck. Beide geben eine <code>ChannelResult<\/code>-Instanz zur\u00fcck, die eines von drei Dingen sein kann: ein erfolgreiches Ergebnis, ein Fehler oder ein Hinweis darauf, dass der Channel geschlossen wurde.<\/p>\n\n\n\n<pre class=\"kotlin-code\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\n\nfun main() = runBlocking&lt;Unit&gt; {\n    val channel = Channel&lt;String&gt;()\n    launch {\n        println(&quot;Sending...&quot;)\n        \/\/ Doesn't suspend\n        \/\/ Returns 'Failed' if the channel is full\n        \/\/ Or 'Channel was closed' result if it's closed\n        val result = channel.trySend(&quot;Element&quot;)\n        println(result)\n\n        \/\/We can verify the result\n        if(result.isClosed){\n            println(&quot;Sending failed. The channel is closed.&quot;)\n        }\n    }\n    println(&quot;Receiving...&quot;)\n    println(channel.tryReceive())\n\/\/  channel.close()\n}\n<\/pre>\n\n\n\n<p>Dieses Beispiel funktioniert genauso wie das vorherige, mit dem einzigen Unterschied, dass <code>tryReceive<\/code> und <code>trySend<\/code> ein detaillierteres Ergebnis zur\u00fcckgeben. Wie Sie sehen k\u00f6nnen, enth\u00e4lt die Ausgabe <code>Value(Failed)<\/code> statt <code>false<\/code> und <code>null<\/code>. Heben wir wie zuvor die Kommentierung der Zeile auf, die den Channel schlie\u00dft, stellen wir fest, dass <code>trySend<\/code> jetzt das Ergebnis <code>Closed<\/code> zur\u00fcckgibt, das eine Ausnahme enth\u00e4lt.<\/p>\n\n\n\n<p>Dank <a href=\"https:\/\/kotlinlang.org\/docs\/inline-classes.html\" target=\"_blank\" rel=\"noopener\">Inline-Werteklassen<\/a> werden bei der Verwendung von <code>ChannelResult<\/code> keine zus\u00e4tzlichen Wrapper erstellt, und wenn eine erfolgreiche Operation einen Wert zur\u00fcckgibt, wird dieser unver\u00e4ndert und ohne Overhead zur\u00fcckgegeben.<\/p>\n\n\n\n<h2>Catching-Funktionen: suspendierende Funktionen mit Fehlerkapselung<\/h2>\n\n\n\n<p>Ab diesem Release tragen suspendierende Methoden mit Fehlerkapselung das Suffix \u201eCatching\u201c im Namen. Die neue Funktion <code>receiveCatching<\/code> zum Beispiel f\u00e4ngt die Ausnahme bei einem geschlossenen Channel ab. Hier ist ein einfaches Beispiel:<\/p>\n\n\n\n<pre class=\"kotlin-code\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\n\nfun main() = runBlocking&lt;Unit&gt; {\n    val channel = Channel&lt;String&gt;()\n    channel.close()\n    println(channel.receiveCatching())\n}\n<\/pre>\n\n\n\n<p>Der Channel wird geschlossen, bevor wir versuchen, einen Wert abzurufen. Das Programm wird jedoch erfolgreich mit dem Hinweis abgeschlossen, dass der Channel geschlossen wurde. Wenn wir <code>receiveCatching<\/code> durch die regul\u00e4re <code>receive<\/code>-Funktion ersetzen, wird die Ausnahme <code>ClosedReceiveChannelException<\/code> ausgel\u00f6st:<\/p>\n\n\n\n<pre class=\"kotlin-code\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\n\nfun main() = runBlocking&lt;Unit&gt; {\n    val channel = Channel&lt;String&gt;()\n    channel.close()\n    println(channel.receive())\n}\n<\/pre>\n\n\n\n<p>Im Moment beschr\u00e4nkt sich das Angebot auf <code>receiveCatching<\/code> und <code>onReceiveCatching<\/code> (anstelle der bisherigen internen Funktion <code>receiveOrClosed<\/code>) \u2013 aber wir haben vor, weitere Funktionen hinzuzuf\u00fcgen.<\/p>\n\n\n\n<h2>Migrieren zu den neuen Funktionen<\/h2>\n\n\n\n<p>Sie k\u00f6nnen alle Verwendungen von <code>offer<\/code> und <code>poll<\/code> in Ihrem Projekt automatisch auf die neuen Funktionen umstellen. Da <code>offer<\/code> einen <code>Boolean<\/code>-Wert zur\u00fcckgab, bietet <code>channel.trySend(&quot;Element&quot;).isSuccess<\/code> gleichwertigen Ersatz.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"800\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2021\/05\/cor15-offer-deprecated.png\" alt=\"\" class=\"wp-image-142625\"\/><\/figure>\n\n\n\n<p><code>poll<\/code> wiederum gibt ein nullbares Element zur\u00fcck, sodass der entsprechende Ersatz <code>channel.tryReceive().getOrNull()<\/code> lautet.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1440\" height=\"440\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2021\/05\/cor15-poll-deprecated.png\" alt=\"\" class=\"wp-image-142636\"\/><\/figure>\n\n\n\n<p>Wenn das Ergebnis des Aufrufs nicht weiterverwendet wurde, k\u00f6nnen Sie den Aufruf direkt durch die neue Funktion ersetzen.<\/p>\n\n\n\n<p>Das Verhalten bei der Ausnahmenbehandlung ist jetzt anders, sodass Sie die erforderlichen Updates manuell vornehmen m\u00fcssen. Wenn sich Ihr Code darauf verl\u00e4sst, dass \u201eoffer\u201c und \u201epoll\u201c bei einem geschlossenen Channel eine Ausnahme ausl\u00f6sen, m\u00fcssen Sie den folgenden Code als Ersatz verwenden.<\/p>\n\n\n\n<p>Der \u00e4quivalente Ersatz f\u00fcr <code>channel.offer(&quot;Element&quot;)<\/code> sollte bei geschlossenem Channel eine Ausnahme ausl\u00f6sen, selbst wenn der Channel normal geschlossen wurde:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nchannel\n  .trySend(&quot;Element&quot;)\n  .onClosed { throw it ?: ClosedSendChannelException(&quot;Channel was closed&quot;) }\n  .isSuccess\n<\/pre>\n\n\n\n<p>Der \u00e4quivalente Ersatz f\u00fcr <code>channel.poll()<\/code> l\u00f6st eine Ausnahme aus, wenn der Channel mit einem Fehler geschlossen wurde und gibt <code>null<\/code> zur\u00fcck, wenn er normal geschlossen wurde:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nchannel.tryReceive()\n  .onClosed { if (it != null) throw it }\n  .getOrNull()\n<\/pre>\n\n\n\n<p>Dieser Code entspricht dem alten Verhalten der Funktionen <code>offer<\/code> und <code>poll<\/code>.<\/p>\n\n\n\n<p>Wir gehen davon aus, dass Sie diese Feinheiten im Verhalten bei einem geschlossenen Channel in den meisten F\u00e4llen nicht absichtlich nutzten, sondern dass es sich eher um eine Fehlerquelle handelte. Aus diesem Grund wird die Semantik durch die automatischen Ersetzungen, die von der IDE angeboten werden, vereinfacht. Wenn dies in Ihrem Fall nicht zutrifft, sollten Sie Ihre Verwendungen manuell \u00fcberpr\u00fcfen und aktualisieren. Eventuell ist dann zu erw\u00e4gen, diese Stellen komplett neu zu schreiben, um F\u00e4lle von geschlossenen Channels ohne Ausnahmen zu verarbeiten.<\/p>\n\n\n\n<h1 id=\"reactive\">Reactive-Integrationen auf dem Weg zur Stabilit\u00e4t<\/h1>\n\n\n\n<p>Version 1.5 von Kotlin Coroutines erhebt die meisten Funktionen, die f\u00fcr die Integration mit Reactive-Frameworks zust\u00e4ndig sind, in den Status einer stabilen API.<\/p>\n\n\n\n<p>Im JVM-\u00d6kosystem gibt es mehrere Frameworks, die sich um die Verarbeitung von asynchronen Streams gem\u00e4\u00df dem <a href=\"https:\/\/www.reactive-streams.org\/\" target=\"_blank\" rel=\"noopener\">Reactive-Streams<\/a>-Standard k\u00fcmmern. Zwei popul\u00e4re Java-Frameworks in diesem Bereich sind <a href=\"https:\/\/projectreactor.io\/\" target=\"_blank\" rel=\"noopener\">Project Reactor<\/a> und <a href=\"https:\/\/github.com\/ReactiveX\/RxJava\" target=\"_blank\" rel=\"noopener\">RxJava<\/a>.<\/p>\n\n\n\n<p><a href=\"https:\/\/kotlinlang.org\/docs\/flow.html\" target=\"_blank\" rel=\"noopener\">Kotlin-Flows<\/a> sind zwar anders, und die Typen sind nicht mit dem Standard kompatibel, aber sie sind dem Konzept nach trotzdem Streams. <code>Flow<\/code> l\u00e4sst sich in den reaktiven (spezifikations- und <a href=\"https:\/\/github.com\/reactive-streams\/reactive-streams-jvm\/tree\/master\/tck\" target=\"_blank\" rel=\"noopener\">TCK<\/a>-konformen) <code>Publisher<\/code> und umgekehrt konvertieren. <code>kotlinx.coroutines<\/code> bringt solche Konverter von Haus aus mit; sie sind in den entsprechenden Reactive-Modulen zu finden.<\/p>\n\n\n\n<p>Wenn Sie beispielsweise Interoperabilit\u00e4t mit den Typen von Project Reactor ben\u00f6tigen, f\u00fcgen Sie Ihrem Projekt die folgenden Abh\u00e4ngigkeiten hinzu:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\ndependencies {          \n    implementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}&quot;)\n    implementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}&quot;)\n}\n<\/pre>\n\n\n\n<p>Dann k\u00f6nnen Sie <code>Flow&lt;T&gt;.asPublisher()<\/code> verwenden, wenn Sie die <em>Reactive-Streams<\/em>-Typen ben\u00f6tigen, oder aber <code>Flow&lt;T&gt;.asFlux()<\/code>, um direkt die Project-Reactor-Typen zu erhalten.<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\n\/\/ acquire a Flow instance\nval flow: Flow&lt;event&gt; = flow { \u2026 }\n\n\/\/ Convert Flow to Publisher\nval publisher = flow.asPublisher()\n\n\/\/ Convert Flow to Reactor's Flux\nval flux = flow.asFlux()\n\n\/\/Convert back to Flow \nval anotherFlow = flux.asFlow()\n<\/pre>\n\n\n\n<p>Dies war ein sehr knapper Abriss zu diesem Thema. Um mehr zu erfahren, empfehlen wir Ihnen den <a href=\"https:\/\/elizarov.medium.com\/reactive-streams-and-kotlin-flows-bfd12772cda4\" target=\"_blank\" rel=\"noopener\">Artikel von Roman Elizarov zu Reactive Streams und Kotlin-Flows<\/a>.<\/p>\n\n\n\n<p>Die Integrationen mit Reactive-Bibliotheken arbeiten zwar auf die Stabilisierung der API hin, aber technisch betrachtet besteht das Ziel darin, die <code>@ExperimentalCoroutinesApi<\/code> aufzul\u00f6sen und die verbliebenen L\u00fccken zu schlie\u00dfen.<\/p>\n\n\n\n<h2>Verbesserte Integration mit Reactive Streams<\/h2>\n\n\n\n<p>Die Kompatibilit\u00e4t mit der Reactive-Streams-Spezifikation ist wichtig f\u00fcr die Interoperabilit\u00e4t zwischen Drittanbieter-Frameworks und Kotlin-Coroutinen. Sie hilft dabei, Kotlin-Coroutinen in Bestandsprojekte zu \u00fcbernehmen, ohne den gesamten Code neu schreiben zu m\u00fcssen.<\/p>\n\n\n\n<p>Es gibt eine <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/commit\/47a063c0987177551bdbdf09a458998a30571ac2\" target=\"_blank\" rel=\"noopener\">lange Liste von Funktionen<\/a>, die dieses Mal den Sprung in den stabilen Status geschafft haben. Es ist jetzt m\u00f6glich, Typen von einer beliebigen Reactive-Streams-Implementierung in <code>Flow<\/code> und wieder zur\u00fcck zu konvertieren. Zum Beispiel kann der neue Code mit Coroutines geschrieben, aber mittels R\u00fcckkonvertierung in eine alte Reactive-Codebasis integriert werden:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nfun legacyFunThatHaveToReturnObservable(): Observable&lt;int&gt; {\n  return flow&lt;int&gt; {\n    \/\/ Use the power of flow!\n  }\n  \/\/ various flow operations\n  .asObservable()\n}\n<\/pre>\n\n\n\n<p>Wir haben auch zahlreiche <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/pull\/2622\" target=\"_blank\" rel=\"noopener\">Verbesserungen<\/a> an <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/blob\/master\/reactive\/kotlinx-coroutines-reactor\/src\/ReactorContext.kt\" target=\"_blank\" rel=\"noopener\"><code>ReactorContext<\/code><\/a> vorgenommen, der einen Reactor-<a href=\"https:\/\/projectreactor.io\/docs\/core\/release\/reference\/#context\" target=\"_blank\" rel=\"noopener\">Context<\/a> in einen <a href=\"https:\/\/kotlinlang.org\/docs\/coroutine-context-and-dispatchers.html\" target=\"_blank\" rel=\"noopener\">CoroutineContext<\/a> verpackt, um eine nahtlose Integration zwischen Project Reactor und Kotlin-Coroutinen zu erm\u00f6glichen. Mit dieser Integration ist es m\u00f6glich, Informationen zum Reactor-Context \u00fcber Coroutinen weiterzuleiten.<\/p>\n\n\n\n<p>Alle Reactive-Integrationen wie <code>Mono<\/code>, <code>Flux<\/code>, <code>Publisher.asFlow<\/code>, <code>Flow.asPublisher<\/code> und <code>Flow.asFlux<\/code> \u00fcbermitteln den Kontext implizit durch den Kontext der Subscriber. Hier ist ein einfaches Beispiel, in dem der Subscriber-<code>Context<\/code> zu <code>ReactorContext<\/code> weitergeleitet wird:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.reactor.ReactorContext\nimport kotlinx.coroutines.reactor.asFlux\n\nfun main() {\n    val flow = flow&lt;int&gt; {\n       println(&quot;Reactor context in Flow: &quot; +\n          currentCoroutineContext()[ReactorContext]?.context)\n    }\n\n    \/\/ No context\n    \/\/ prints &quot;Reactor context in Flow: null&quot;\n    flow.asFlux().subscribe() \n\n    \/\/ Add subscriber's context\n    \/\/ prints &quot;Reactor context in Flow: Context1{answer=42}&quot;\n    flow.asFlux()\n        .contextWrite { ctx -&gt; ctx.put(&quot;answer&quot;, 42) }\n        .subscribe() \n}\n<\/pre>\n\n\n\n<p>Im obigen Beispiel erstellen wir eine <code>Flow<\/code>-Instanz, die dann ohne Kontext in eine Reactor-Flux-Instanz konvertiert wird. Durch den Aufruf der Methode <code>subscribe()<\/code> ohne Argument wird der Publisher zur \u00dcbermittlung <em>aller<\/em> Daten aufgefordert. Als Ergebnis gibt das Programm \u201e<em>Reactor context in Flow: null<\/em>\u201c aus.<\/p>\n\n\n\n<p>Auch die n\u00e4chste Aufrufkette konvertiert <code>Flow<\/code> in <code>Flux<\/code>, f\u00fcgt dann aber dem Reactor-Kontext dieser Kette den Schl\u00fcssel-Wert-Paar <em>answer=42<\/em> hinzu. Der Aufruf von <code>subscribe()<\/code> l\u00f6st die Kette aus. Da der Kontext nicht leer ist, gibt das Programm diesmal \u201e<em>Reactor context in Flow: Context1{answer=42}<\/em>\u201c aus.<\/p>\n\n\n\n<h2>Neue Hilfsfunktionen<\/h2>\n\n\n\n<p>Bei der Arbeit mit reaktiven Typen wie <code>Mono<\/code> im Kontext von Coroutinen gibt es einige Hilfsfunktionen, die den Abruf ohne Blockieren des Threads erm\u00f6glichen. In dieser Version haben wir <code>awaitSingleOr<em><\/code>-Funktionen auf arbitr\u00e4ren <code>Publisher<\/code>n als <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/pull\/2628\" target=\"_blank\" rel=\"noopener\">veraltet<\/a> markiert und einige <code>await<\/em><\/code>-Funktionen f\u00fcr <code>Mono<\/code> und <code>Maybe<\/code> spezialisiert.<\/p>\n\n\n\n<p><code>Mono<\/code> erzeugt h\u00f6chstens einen Wert, daher sind erstes und letztes Element identisch. Auch die Semantik zum Verwerfen der verbleibenden Elemente ist in diesem Fall wenig hilfreich. Deshalb wurden <code>Mono.awaitFirst()<\/code> und <code>Mono.awaitLast()<\/code> zugunsten von <code>Mono.awaitSingle()<\/code> als veraltet gekennzeichnet.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"906\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2021\/05\/cor15-reactive-streams-awaitFirst.png\" alt=\"\" class=\"wp-image-142647\"\/><\/figure>\n\n\n\n<h1 id=\"use-coroutines-1-5-0\">Legen Sie mit kotlinx.coroutines 1.5.0 los! <\/h1>\n\n\n\n<p>Die neue Version enth\u00e4lt eine beeindruckende Liste von Neuerungen. Eine besonders bemerkenswerte Leistung des Teams ist das neue Namensschema, das im Zuge der Channels-API-Optimierung entwickelt wurde. Gleichzeitig liegt uns sehr viel daran, die Coroutines-API so einfach und intuitiv wie m\u00f6glich zu gestalten.<\/p>\n\n\n\n<p>Um die neue Version von Kotlin Coroutines zu verwenden, aktualisieren Sie einfach den Inhalt Ihrer build.gradle.kts-Datei. Stellen Sie als Erstes sicher, dass Sie die neueste Version des Kotlin-Gradle-Plugins verwenden:<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nplugins {\n   kotlin(&quot;jvm&quot;) version &quot;1.5.0&quot;\n}\n<\/pre>\n\n\n\n<p>Aktualisieren Sie anschlie\u00dfend die Versionen der Abh\u00e4ngigkeiten, einschlie\u00dflich der Bibliotheken mit speziellen Integrationen f\u00fcr Reactive Streams.<\/p>\n\n\n\n<pre class=\"kotlin-code\" data-highlight-only=\"true\" theme=\"idea\" indent=\"4\" style=\"visibility: hidden; padding: 36px 0;\">\nval coroutinesVersion = &quot;1.5.0&quot;\n\ndependencies { \n  implementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:$coroutinesVersion&quot;)\n  implementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutinesVersion&quot;)\n  implementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutinesVersion&quot;)\n  implementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$coroutinesVersion&quot;)\n  ...\n}\n<\/pre>\n\n\n\n<h1>Interessantes zum Lesen und Ansehen<\/h1>\n\n\n\n<ul><li><a href=\"https:\/\/youtu.be\/EVLnWOcR0is\" class=\"ek-link\" target=\"_blank\" rel=\"noopener\">Video zu Kotlin Coroutines 1.5.0<\/a><\/li><li><a href=\"https:\/\/kotlinlang.org\/docs\/coroutines-guide.html\" target=\"_blank\" rel=\"noopener\">Leitfaden zu Coroutinen<\/a><\/li><li><a href=\"https:\/\/kotlin.github.io\/kotlinx.coroutines\/\" target=\"_blank\" rel=\"noopener\">API-Dokumentation<\/a><\/li><li><a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\" target=\"_blank\" rel=\"noopener\">Das GitHub-Repository von Kotlin Coroutines<\/a><\/li><li><a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2020\/10\/kotlinx-coroutines-1-4-0-introducing-stateflow-and-sharedflow\/\" class=\"ek-link\">Blogartikel zu Coroutines 1.4.0<\/a><\/li><li><a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-1-5-0-released\/\" class=\"ek-link\">Blogartikel zu Kotlin 1.5.0<\/a><\/li><\/ul>\n\n\n\n<h3>Hilfe bei Problemen<\/h3>\n\n\n\n<ul><li>Bitte melden Sie Probleme in unserem <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/issues\" target=\"_blank\" rel=\"noopener\">Issue-Tracker auf GitHub<\/a>.<\/li><li>Unterst\u00fctzung finden Sie im #coroutines-Kanal des Kotlin-Slacks (<a href=\"https:\/\/surveys.jetbrains.com\/s3\/kotlin-slack-sign-up\" target=\"_blank\" rel=\"noopener\">eine Einladung erhalten Sie hier<\/a>).<\/li><\/ul>\n","protected":false},"author":820,"featured_media":142994,"comment_status":"closed","ping_status":"closed","template":"","categories":[909],"tags":[671],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/kotlin\/145835"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/kotlin"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/types\/kotlin"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/users\/820"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/comments?post=145835"}],"version-history":[{"count":4,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/kotlin\/145835\/revisions"}],"predecessor-version":[{"id":145875,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/kotlin\/145835\/revisions\/145875"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/media\/142994"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/media?parent=145835"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/categories?post=145835"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/tags?post=145835"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/cross-post-tag?post=145835"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}