{"id":152942,"date":"2021-06-10T11:38:00","date_gmt":"2021-06-10T10:38:00","guid":{"rendered":"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-coroutines-1-5-0-released\/"},"modified":"2021-06-10T11:39:33","modified_gmt":"2021-06-10T10:39:33","slug":"kotlin-coroutines-1-5-0-released","status":"publish","type":"kotlin","link":"https:\/\/blog.jetbrains.com\/fr\/kotlin\/2021\/06\/kotlin-coroutines-1-5-0-released\/","title":{"rendered":"Kotlin Coroutines 1.5\u00a0: GlobalScope signal\u00e9e comme API &#8220;sensible&#8221;, am\u00e9lioration de l&#8217;API de canaux, et plus encore"},"content":{"rendered":"\n<p>La version 1.5.0 de Kotlin Coroutines est l\u00e0&nbsp;! Voici ce que cette nouvelle version apporte :<\/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 est maintenant signal\u00e9e comme API &#8220;sensible&#8221;, n\u00e9cessitant une attention particuli\u00e8re.<\/a> Elle offre en effet des fonctionnalit\u00e9s avanc\u00e9es qui peuvent facilement donner lieu \u00e0 une utilisation incorrecte. Dor\u00e9navant, le compilateur vous avertit en cas de risque d&#8217;utilisation incorrecte et un opt-in sera requis pour l&#8217;utilisation de cette classe dans votre programme.<\/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\">Extensions pour JUnit.<\/a> CoroutinesTimeout est maintenant disponible pour JUnit 5.<\/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\">Am\u00e9lioration de l&#8217;API de canaux.<\/a> En plus des nouvelles r\u00e8gles de nommage pour les fonctions de la biblioth\u00e8que, les fonctions non suspensives <code>trySend<\/code> et <code>tryReceive<\/code> ont \u00e9t\u00e9 introduites comme de meilleures alternatives \u00e0 <code>offer<\/code> et <code>poll<\/code>.<\/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\">Stabilisation des int\u00e9grations Reactive.<\/a> Nous avons ajout\u00e9 plus de fonctions pour la conversion des types Reactive Streams en Kotlin Flow et inversement, et stabilis\u00e9 de nombreuses fonctions existantes et l&#8217;API ReactiveContext.<\/li><\/ul>\n\n\n\n<p>Vous trouverez toutes les recommandations pour passer \u00e0 la nouvelle version en fin d&#8217;article.<\/p>\n\n\n\n<!--more-->\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 marked as delicate, refined Channels API, and more\" 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=\"Use Coroutines 1.5.0\" href=\"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-coroutines-1-5-0-released\/#use-coroutines-1-5-0\">Commencer \u00e0 utiliser Coroutines 1.5.0<\/a><\/p>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"globalscope\">GlobalScope signal\u00e9e comme API \u00e0 appr\u00e9hender avec pr\u00e9caution <\/h1>\n\n\n\n<p>La classe <code>GlobalScope<\/code> est d\u00e9sormais marqu\u00e9e avec l&#8217;annotation <code>@DelicateCoroutinesApi<\/code>. Dor\u00e9navant, toute utilisation de <code>GlobalScope<\/code> n\u00e9cessitera un opt-in explicite avec <code>@OptIn(DelicateCoroutinesApi::class)<\/code>.<\/p>\n\n\n\n<p>Bien que l&#8217;utilisation de <code>GlobalScope<\/code> ne soit pas recommand\u00e9e dans la plupart des cas, la <a href=\"https:\/\/kotlinlang.org\/docs\/coroutines-basics.html#your-first-coroutine\" target=\"_blank\" rel=\"noopener\">documentation officielle<\/a> propose un certain nombre de concepts utilisant cette API.<\/p>\n\n\n\n<p>Un <code>CoroutineScope<\/code> global n&#8217;est li\u00e9 \u00e0 aucune t\u00e2che. Global scope est utilis\u00e9e pour ex\u00e9cuter des coroutines de niveau sup\u00e9rieur qui fonctionnent pendant toute la dur\u00e9e de vie de l&#8217;application et ne sont pas annul\u00e9es pr\u00e9matur\u00e9ment. Les coroutines actives lanc\u00e9es dans <code>GlobalScope<\/code> ne permettent pas d&#8217;\u00e9viter l&#8217;arr\u00eat du processus. Elles sont similaires \u00e0 des threads d\u00e9mons.<\/p>\n\n\n\n<p>L&#8217;utilisation de l&#8217;API <code>GlobalScope<\/code> est d\u00e9licate et requiert de la prudence car elle peut facilement engendrer des pertes de ressources ou de m\u00e9moire. Une coroutine lanc\u00e9e dans <code> GlobalScope <\/code> n&#8217;ob\u00e9it pas au principe de concurrence structur\u00e9e, donc en cas de blocage ou de ralentissement (en raison de la lenteur du r\u00e9seau par exemple), elle continuera de fonctionner et de consommer des ressources. Voici un exemple :<\/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>L&#8217;appel \u00e0 <code> loadConfiguration <\/code> cr\u00e9e une coroutine dans <code> GlobalScope <\/code> qui s&#8217;ex\u00e9cute en arri\u00e8re-plan et aucune condition n&#8217;est sp\u00e9cifi\u00e9e pour l&#8217;annuler ou attendre son ach\u00e8vement. Si le r\u00e9seau est lent, elle reste en attente en arri\u00e8re-plan et consomme des ressources. Des appels r\u00e9p\u00e9t\u00e9s \u00e0 <code>loadConfiguration<\/code> entra\u00eeneront la consommation de plus en plus de ressources.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Possibilit\u00e9s de remplacement<\/h2>\n\n\n\n<p>Dans de nombreux cas, l&#8217;utilisation de <code>GlobalScope<\/code> doit \u00eatre \u00e9vit\u00e9e et l&#8217;op\u00e9ration contenante doit \u00eatre marqu\u00e9e avec <code>suspend<\/code>, par exemple&nbsp;:<\/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>Dans les cas o\u00f9 <code>GlobalScope.launch<\/code> est utilis\u00e9 pour lancer plusieurs op\u00e9rations simultan\u00e9es, les op\u00e9rations correspondantes doivent plut\u00f4t \u00eatre regroup\u00e9es avec <code>coroutineScope<\/code>&nbsp;:<\/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>Dans le code de niveau sup\u00e9rieur, lors du lancement d&#8217;une op\u00e9ration concurrente simultan\u00e9e \u00e0 partir d&#8217;un contexte non suspensif, utilisez une instance <code>CoroutineScope<\/code> correctement d\u00e9limit\u00e9e au lieu de <code>GlobalScope<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Cas d&#8217;utilisation appropri\u00e9s<\/h2>\n\n\n\n<p>L&#8217;utilisation de <code>GlobalScope<\/code> est s\u00fbre et justifi\u00e9e dans quelques cas, parmi lesquels les processus d&#8217;arri\u00e8re-plan de niveau sup\u00e9rieur qui doivent s&#8217;ex\u00e9cuter pendant toute la dur\u00e9e de vie d&#8217;une application. C&#8217;est pourquoi toute utilisation de <code>GlobalScope<\/code> requiert un opt-in explicite avec <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>Nous vous recommandons de passer en revue toutes vos utilisations de <code>GlobalScope<\/code> et d&#8217;annoter uniquement celles qui rentrent dans la cat\u00e9gorie des \u00ab&nbsp;cas d&#8217;utilisation appropri\u00e9s&nbsp;\u00bb. Pour tous les autres cas, pour \u00e9viter les bugs dans votre code, il faut remplacer l&#8217;utilisation de <code>GlobalScope<\/code> comme d\u00e9crit ci-dessus.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"junit\">Extensions pour JUnit 5<\/h1>\n\n\n\n<p>Nous avons ajout\u00e9 une annotation <code>CoroutinesTimeout<\/code> qui vous permet d&#8217;ex\u00e9cuter des tests dans un thread s\u00e9par\u00e9, de les d\u00e9sactiver une fois le temps imparti expir\u00e9 et d&#8217;interrompre le thread. Auparavant, <code>CoroutinesTimeout<\/code> \u00e9tait seulement disponible pour JUnit 4. Avec cette version, nous avons ajout\u00e9 l&#8217;int\u00e9gration pour JUnit 5.<\/p>\n\n\n\n<p>Pour utiliser la nouvelle annotation, ajoutez la d\u00e9pendance suivante \u00e0 votre projet&nbsp;:<\/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>Voici un exemple simple de l&#8217;utilisation de <code>CoroutinesTimeout<\/code> dans vos tests&nbsp;:<\/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>Dans cet exemple, le d\u00e9lai d&#8217;expiration des coroutines est d\u00e9fini au niveau de la classe et sp\u00e9cifiquement pour <code>firstTest<\/code>. Il n&#8217;y a pas de d\u00e9lai imparti pour le test annot\u00e9 car l&#8217;annotation de la fonction pr\u00e9vaut sur celle de la classe. En revanche il y en a un pour <code>secondTest<\/code>, qui utilise l&#8217;annotation au niveau de la classe.<\/p>\n\n\n\n<p>L&#8217;annotation est d\u00e9clar\u00e9e de la fa\u00e7on suivante&nbsp;:<\/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>Le premier param\u00e8tre, <code>testTimeoutMs<\/code>, sp\u00e9cifie la dur\u00e9e du d\u00e9lai imparti en millisecondes. Le deuxi\u00e8me param\u00e8tre, <code>cancelOnTimeout<\/code>, d\u00e9termine si toutes les coroutines en cours d&#8217;ex\u00e9cution doivent \u00eatre annul\u00e9es \u00e0 la fin du d\u00e9lai imparti. S&#8217;il est d\u00e9fini comme <code>true<\/code>, toutes les coroutines seront automatiquement annul\u00e9es.<\/p>\n\n\n\n<p>Lorsque vous utilisez l&#8217;annotation <code>CoroutinesTimeout<\/code>, elle active automatiquement le <a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2020\/07\/kotlin-1-4-rc-debugging-coroutines\/\">d\u00e9bogueur de coroutines<\/a> et cr\u00e9e un dump de toutes les coroutines lorsque le d\u00e9lai imparti a expir\u00e9. Le dump contient les traces de pile de la cr\u00e9ation des coroutines. Si vous devez d\u00e9sactiver les traces de pile afin d&#8217;acc\u00e9l\u00e9rer les tests vous pouvez utiliser <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> directement pour d\u00e9finir les param\u00e8tres appropri\u00e9s.<\/p>\n\n\n\n<p>Un grand merci \u00e0 <a href=\"https:\/\/github.com\/asarkar\" target=\"_blank\" rel=\"noopener\">Abhijit Sarkar<\/a> pour son PoC particuli\u00e8rement utile pour <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/issues\/2197\" class=\"ek-link\" target=\"_blank\" rel=\"noopener\">CoroutinesTimeout pour JUnit 5<\/a>. L&#8217;id\u00e9e a \u00e9t\u00e9 d\u00e9velopp\u00e9e dans la nouvelle annotation <code>CoroutinesTimeout<\/code> que nous avons ajout\u00e9e dans la version 1.5.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"channels\">Am\u00e9lioration de l&#8217;API de canaux<\/h1>\n\n\n\n<p>Les canaux constituent des primitives de communication importantes, qui permettent l&#8217;\u00e9change de donn\u00e9es entre coroutines et callbacks. Pour cette version, nous avons retravaill\u00e9 l&#8217;API de canaux, en rempla\u00e7ant les fonctions <code>offer<\/code> et <code>poll<\/code>, qui pr\u00eataient \u00e0 confusion, par de meilleures alternatives. Au passage, nous avons d\u00e9velopp\u00e9 un nouveau syst\u00e8me de nommage coh\u00e9rent pour les m\u00e9thodes suspensives et non suspensives.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Nouveau sch\u00e9ma de nommage<\/h2>\n\n\n\n<p>Nous avons voulu cr\u00e9er des r\u00e8gles de nommage coh\u00e9rentes qui pourraient ensuite \u00eatre utilis\u00e9es dans d&#8217;autres biblioth\u00e8ques ou API de coroutine. Nous devions nous assurer que le nom de la fonction transmettrait les informations sur son comportement. Voici ce \u00e0 quoi nous sommes parvenus :<\/p>\n\n\n\n<ul><li>Les m\u00e9thodes suspensives r\u00e9guli\u00e8res sont laiss\u00e9es telles quelles, par exemple <code>send<\/code> et <code>receive<\/code>.<\/li><li>Tous les noms des m\u00e9thodes non suspensives avec encapsulation d&#8217;erreur sont syst\u00e9matiquement pr\u00e9fix\u00e9s par \u00ab&nbsp;try&nbsp;\u00bb&nbsp;: <code>trySend<\/code> et <code>tryReceive<\/code> au lieu de <code>offer<\/code> et <code>poll<\/code>.<\/li><li>Les nouvelles m\u00e9thodes suspensives d&#8217;encapsulation d&#8217;erreur auront le suffixe \u00ab&nbsp;Catching&nbsp;\u00bb.<\/li><\/ul>\n\n\n\n<p>Voyons ces nouvelles m\u00e9thodes plus en d\u00e9tails.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fonctions <code>Try<\/code>&nbsp;: \u00e9quivalents non suspensifs de <code>send<\/code> et <code>receive<\/code><\/h2>\n\n\n\n<p>Une coroutine peut envoyer des informations \u00e0 un canal, tandis que l&#8217;autre peut recevoir ces informations de ce canal. Les fonctions <code>send<\/code> et <code>receive<\/code> sont toutes les deux suspensives. <code>send<\/code> suspend sa coroutine si le canal est plein et ne peut pas prendre en compte de nouvel \u00e9l\u00e9ment et <code>receive<\/code> suspend sa coroutine si le canal n&#8217;a aucun \u00e9l\u00e9ment \u00e0 retourner&nbsp;:<\/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>Ces fonctions ont des \u00e9quivalents non suspensifs pour une utilisation dans du code synchrone&nbsp;: <code>offer<\/code> et <code>poll<\/code>, qui sont remplac\u00e9s par <code> trySend <\/code> et <code> tryReceive <\/code>, et la prise en charge de l&#8217;ancienne fonctionnalit\u00e9 est interrompue. Voyons quelles sont les raisons de ce changement.<\/p>\n\n\n\n<p>Les fonctions <code>offer<\/code> et <code>poll<\/code> sont cens\u00e9es avoir le m\u00eame comportement que <code>send<\/code> et <code>receive<\/code>, mais sans suspension. Cela semble simple et c&#8217;est le cas tant que l&#8217;\u00e9l\u00e9ment peut \u00eatre envoy\u00e9 ou re\u00e7u. Mais qu&#8217;arriverait-t-il en cas d&#8217;erreur&nbsp;? <code>send<\/code> et <code>receive<\/code> seraient alors suspendues jusqu&#8217;\u00e0 ce qu&#8217;elles puissent de nouveau fonctionner correctement. <code>offer<\/code> et <code>poll<\/code> retournaient simplement <code>false<\/code> et <code>null<\/code> respectivement si l&#8217;\u00e9l\u00e9ment n&#8217;avait pas pu \u00eatre ajout\u00e9 parce que le canal \u00e9tait plein ou si aucun \u00e9l\u00e9ment n&#8217;avait pu \u00eatre r\u00e9cup\u00e9r\u00e9 car le canal \u00e9tait vide. Elles ont tous les deux lanc\u00e9 une exception pour tenter de travailler avec un canal ferm\u00e9, ce qui a caus\u00e9 des probl\u00e8mes avec leur utilisation.<\/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&#039;t suspend\n        \/\/ Returns &#039;false&#039; if the channel is full\n        \/\/ Or throws an exception if it&#039;s closed\n        channel.offer(&quot;Element&quot;)\n    }\n    println(&quot;Receiving...&quot;)\n    \/\/ Doesn&#039;t suspend\n    \/\/ Returns &#039;null&#039; if the channel is empty\n    println(channel.poll())\n\/\/  channel.close()\n}\n<\/pre>\n\n\n\n<p>Dans cet exemple, <code>poll<\/code> est appel\u00e9 avant qu&#8217;un \u00e9l\u00e9ment ne soit ajout\u00e9 et renvoie donc <code>null<\/code> imm\u00e9diatement. Notez que cette fonction n&#8217;est pas cens\u00e9e \u00eatre utilis\u00e9e de cette fa\u00e7on&nbsp;: il est pr\u00e9f\u00e9rable de continuer \u00e0 interroger les \u00e9l\u00e9ments r\u00e9guli\u00e8rement. Nous l&#8217;appelons directement pour simplifier cette explication. L&#8217;appel de <code>offer<\/code> est \u00e9galement infructueux car il s&#8217;agit d&#8217;un canal rendezvous ayant une capacit\u00e9 de m\u00e9moire tampon nulle. Par cons\u00e9quent, <code>offer<\/code> renvoie <code>false<\/code> et <code>poll<\/code> renvoie <code>null<\/code>, simplement parce qu&#8217;elles n&#8217;ont pas \u00e9t\u00e9 appel\u00e9es dans bon ordre.<\/p>\n\n\n\n<p>Dans l&#8217;exemple ci-dessus, essayez de d\u00e9commenter l&#8217;instruction <code>channel.close()<\/code> pour vous assurer que l&#8217;exception est lev\u00e9e. Dans ce cas, <code>poll<\/code> renvoie <code>false<\/code>, comme pr\u00e9c\u00e9demment. Mais ensuite <code>offer<\/code> essaie d&#8217;ajouter un \u00e9l\u00e9ment \u00e0 un canal d\u00e9j\u00e0 ferm\u00e9, \u00e9choue et lance une exception. Nous avons re\u00e7u de nombreuses remarques selon lesquelles ce comportement est source d&#8217;erreurs. Il est facile d&#8217;oublier de g\u00e9rer cette exception, or l&#8217;ignorer ou la traiter diff\u00e9remment aura pour cons\u00e9quence de planter votre programme.<\/p>\n\n\n\n<p>Les nouvelles fonctions <code>trySend<\/code> et <code>tryReceive<\/code> corrigent ce probl\u00e8me et renvoient un r\u00e9sultat plus d\u00e9taill\u00e9. Chacune renvoie l&#8217;instance <code>ChannelResult<\/code>, qui peut indiquer trois choses : un r\u00e9sultat positif, un \u00e9chec ou la fermeture du canal.<\/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&#039;t suspend\n        \/\/ Returns &#039;Failed&#039; if the channel is full\n        \/\/ Or &#039;Channel was closed&#039; result if it&#039;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>Cet exemple fonctionne de la m\u00eame mani\u00e8re que le pr\u00e9c\u00e9dent. La seule diff\u00e9rence est que <code>tryReceive<\/code> et <code>trySend<\/code> renvoient un r\u00e9sultat plus d\u00e9taill\u00e9. Vous pouvez voir le r\u00e9sultat <code>Value(Failed)<\/code> au lieu de <code>false<\/code> et <code>null<\/code>. D\u00e9commentez la ligne fermant \u00e0 nouveau le canal et assurez-vous que <code>trySend<\/code> renvoie maintenant un r\u00e9sultat <code>Closed<\/code> capturant une exception.<\/p>\n\n\n\n<p>Gr\u00e2ce aux <a href=\"https:\/\/kotlinlang.org\/docs\/inline-classes.html\" target=\"_blank\" rel=\"noopener\">classes de valeurs inline<\/a>, l&#8217;utilisation de <code>ChannelResult<\/code> ne cr\u00e9e pas de wrappers suppl\u00e9mentaires en dessous et si la valeur r\u00e9ussie est renvoy\u00e9e, elle l&#8217;est telle quelle, sans surcharge.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fonctions Catching&nbsp;: suspendre les fonctions qui encapsulent des erreurs<\/h2>\n\n\n\n<p>\u00c0 partir de cette version, les m\u00e9thodes suspensives d&#8217;encapsulation des erreurs auront le suffixe \u00ab&nbsp;Catching&nbsp;\u00bb. Par exemple, la nouvelle fonction <code>receiveCatching<\/code> g\u00e8re l&#8217;exception dans le cas d&#8217;un canal ferm\u00e9. Prenons cet exemple simple&nbsp;:<\/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>Le canal est ferm\u00e9 avant que nous essayions de r\u00e9cup\u00e9rer une valeur. Cependant, le programme aboutit avec succ\u00e8s, indiquant que le canal a \u00e9t\u00e9 ferm\u00e9. Si vous remplacez <code>receiveCatching<\/code> par la fonction <code>receive<\/code> ordinaire, elle lancera <code>ClosedReceiveChannelException<\/code>&nbsp;:<\/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>Actuellement, nous fournissons seulement <code>receiveCatching<\/code> et <code>onReceiveCatching<\/code> (au lieu de la fonction interne <code>receiveOrClosed<\/code> auparavant), mais nous pr\u00e9voyons d&#8217;ajouter d&#8217;autres fonctions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Migration de votre code vers de nouvelles fonctions<\/h2>\n\n\n\n<p>Vous pouvez remplacer automatiquement toutes les utilisations des fonctions <code>offer<\/code> et <code>poll<\/code> dans votre projet avec de nouveaux appels. Puisque <code>offer<\/code> a renvoy\u00e9 <code>Boolean<\/code>, son \u00e9quivalent de remplacement est <code>canal.trySend(\"Element\").isSuccess<\/code>.<\/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>De m\u00eame, la fonction <code>poll<\/code> renvoie un \u00e9l\u00e9ment nullable, son remplacement devient donc <code>canal.tryReceive().getOrNull()<\/code>.<\/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>Si le r\u00e9sultat de l&#8217;appel n&#8217;a pas \u00e9t\u00e9 utilis\u00e9, vous pouvez les remplacer directement par de nouveaux appels.<\/p>\n\n\n\n<p>Le comportement de traitement des exceptions est d\u00e9sormais diff\u00e9rent, vous devrez donc effectuer les mises \u00e0 jour n\u00e9cessaires manuellement. Si votre code repose sur les m\u00e9thodes \u00ab&nbsp;offer&nbsp;\u00bb et \u00ab&nbsp;poll&nbsp;\u00bb qui lancent des exceptions sur un canal ferm\u00e9, vous devrez utiliser les remplacements suivants.<\/p>\n\n\n\n<p>Le remplacement \u00e9quivalent pour <code>canal.offer(\"Element\")<\/code> devrait lever une exception lorsque le canal est ferm\u00e9, m\u00eame s&#8217;il a \u00e9t\u00e9 ferm\u00e9 normalement&nbsp;:<\/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>Le remplacement \u00e9quivalent pour <code>channel.poll()<\/code> lance une exception si le canal a \u00e9t\u00e9 ferm\u00e9 avec une erreur et renvoie <code>null<\/code> s&#8217;il a \u00e9t\u00e9 ferm\u00e9 normalement&nbsp;:<\/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>Ces changements correspondent \u00e0 l&#8217;ancien comportement des fonctions <code>offer<\/code> et <code>poll<\/code>.<\/p>\n\n\n\n<p>Nous sommes partis du postulat que, dans la plupart des cas, votre code ne reposait pas sur ces subtilit\u00e9s de comportement sur un canal ferm\u00e9, mais que cela \u00e9tait plut\u00f4t une source de bugs. C&#8217;est pourquoi les remplacements automatiques fournis par l&#8217;IDE simplifient la s\u00e9mantique. Si cela ne correspond pas \u00e0 votre cas, veuillez passer en revue vos utilisations et les mettre \u00e0 jour manuellement et envisager de les r\u00e9\u00e9crire compl\u00e8tement pour traiter les cas de canaux ferm\u00e9s diff\u00e9remment, sans lever d&#8217;exceptions.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"reactive\">Les int\u00e9grations Reactive sur la voie de la stabilit\u00e9<\/h1>\n\n\n\n<p>Avec la version 1.5 de Kotlin Coroutines, la plupart des fonctions responsables des int\u00e9grations avec les frameworks reactive sont maintenant stables.<\/p>\n\n\n\n<p>Dans l&#8217;\u00e9cosyst\u00e8me de la JVM, quelques frameworks traitent la gestion des threads asynchrones selon la norme des <a href=\"https:\/\/www.reactive-streams.org\/\" target=\"_blank\" rel=\"noopener\">Reactive Streams<\/a>. Les frameworks Java <a href=\"https:\/\/projectreactor.io\/\" target=\"_blank\" rel=\"noopener\">Project Reactor<\/a> et <a href=\"https:\/\/github.com\/ReactiveX\/RxJava\" target=\"_blank\" rel=\"noopener\">RxJava<\/a> sont sont de ceux l\u00e0.<\/p>\n\n\n\n<p>Bien que les <a href=\"https:\/\/kotlinlang.org\/docs\/flow.html\" target=\"_blank\" rel=\"noopener\">Kotlin Flows<\/a> soient diff\u00e9rents et que les types ne soient pas compatibles avec ceux sp\u00e9cifi\u00e9s par la norme, ce sont n\u00e9anmoins des flux. Il est possible de convertir <code>Flow<\/code> en Reactive (en conformit\u00e9 avec les sp\u00e9cifications et <a href=\"https:\/\/github.com\/reactive-streams\/reactive-streams-jvm\/tree\/master\/tck\" target=\"_blank\" rel=\"noopener\">TCK<\/a>) <code>Publisher<\/code> et vice versa. Ces convertisseurs sont directement fournis par <code>kotlinx.coroutines<\/code> et peuvent \u00eatre trouv\u00e9s dans les modules Reactive correspondants.<\/p>\n\n\n\n<p>Par exemple, si vous avez besoin d&#8217;interop\u00e9rabilit\u00e9 avec les types de Project Reactor, vous devez ajouter les d\u00e9pendances suivantes \u00e0 votre projet&nbsp;:<\/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>Vous pourrez alors utiliser <code>Flow&lt;T&gt;.asPublisher()<\/code> si vous voulez utiliser les types <em>Reactive Streams<\/em> ou <code>Flow&lt;T&gt;.asFlux()<\/code> si vous devez utiliser directement les types Project Reactor.<\/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&#039;s Flux\nval flux = flow.asFlux()\n\n\/\/Convert back to Flow \nval anotherFlow = flux.asFlow()\n<\/pre>\n\n\n\n<p>Pour en savoir plus sur les Reactive Streams et les Kotlin Flows, consultez l&#8217;article de <a href=\"https:\/\/elizarov.medium.com\/reactive-streams-and-kotlin-flows-bfd12772cda4\" target=\"_blank\" rel=\"noopener\">Roman Elizarov<\/a>.<\/p>\n\n\n\n<p>Bien que les int\u00e9grations avec les biblioth\u00e8ques Reactive contribuent \u00e0 la stabilisation de l&#8217;API, d&#8217;un point de vue technique, l&#8217;objectif est de se d\u00e9barrasser des <code>@ExperimentalCoroutinesApi<\/code> et de corriger les bugs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Meilleure int\u00e9gration avec Reactive Streams<<\/h2>\n\n\n\n<p>La compatibilit\u00e9 avec les sp\u00e9cifications de Reactive Streams est importante afin d&#8217;assurer l&#8217;interop\u00e9rabilit\u00e9 entre les frameworks tiers et les coroutines Kotlin. Cela permet d&#8217;adopter les coroutines Kotlin dans les projets h\u00e9rit\u00e9s sans avoir \u00e0 r\u00e9\u00e9crire tout le code.<\/p>\n\n\n\n<p>Nous sommes parvenus \u00e0 faire \u00e9voluer et \u00e0 stabiliser <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/commit\/47a063c0987177551bdbdf09a458998a30571ac2\" target=\"_blank\" rel=\"noopener\">de nombreuses fonctions<\/a>. Il est maintenant possible de convertir un type de n&#8217;importe quelle impl\u00e9mentation Reactive Streams en <code>Flow<\/code> et inversement. Par exemple, le nouveau code peut \u00eatre \u00e9crit avec des coroutines, mais int\u00e9gr\u00e9 \u00e0 l&#8217;ancienne base de code Reactive via les convertisseurs oppos\u00e9s&nbsp;:<\/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>Nous avons \u00e9galement apport\u00e9 de nombreuses <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/pull\/2622\" target=\"_blank\" rel=\"noopener\">am\u00e9liorations<\/a> \u00e0<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>, qui encapsule le <a href=\"https:\/\/projectreactor.io\/docs\/core\/release\/reference\/#context\" target=\"_blank\" rel=\"noopener\">Context<\/a> de Reactor dans <a href=\"https:\/\/kotlinlang.org\/docs\/coroutine-context-and-dispatchers.html\" target=\"_blank\" rel=\"noopener\">CoroutineContext<\/a>, pour une int\u00e9gration fluide et compl\u00e8te entre Project Reactor et Kotlin Coroutines. Gr\u00e2ce \u00e0 cette int\u00e9gration, il est possible de propager les informations du Context de Reactor \u00e0 travers des coroutines.<\/p>\n\n\n\n<p>Le contexte est implicitement propag\u00e9 via le contexte des subscribers par toutes les int\u00e9grations Reactive, telles que <code>Mono<\/code>, <code>Flux<\/code>, <code>Publisher.asFlow<\/code>, <code>Flow.asPublisher<\/code> et <code>Flow.asFlux<\/code>. Voici un exemple simple de distribution du <code>context<\/code> du subscriber dans <code>ReactorContext<\/code>&nbsp;:<\/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&#039;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>Dans l&#8217;exemple ci-dessus, nous construisons une instance <code>Flow<\/code> qui est ensuite convertie en instance Flux de Reactor, sans contexte. Appeler la m\u00e9thode <code>subscribe()<\/code> sans argument a pour cons\u00e9quence de demander au publisher d&#8217;envoyer <em>toutes<\/em> les donn\u00e9es. En cons\u00e9quence, le programme affiche la phrase \u00ab&nbsp;<em>Reactor context in Flow: null<\/em>&nbsp;\u00bb.<\/p>\n\n\n\n<p>De m\u00eame, la cha\u00eene d&#8217;appels suivante convertit <code>Flow<\/code> en <code>Flux<\/code>, mais ajoute ensuite une paire cl\u00e9-valeur <em>r\u00e9ponse=42<\/em> au contexte Reactor pour cette cha\u00eene. L&#8217;appel \u00e0 <code>subscribe()<\/code> d\u00e9clenche la cha\u00eene. Dans ce cas, puisque le contexte est renseign\u00e9, le programme affiche \u00ab&nbsp;<em>Reactor context in Flow: Context1{answer=42}<\/em>&nbsp;\u00bb.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Nouvelles fonctions d&#8217;assistance<\/h2>\n\n\n\n<p>Lorsque vous utilisez des types r\u00e9actifs comme <code>Mono<\/code> dans le contexte des coroutines, des fonctions d&#8217;aide vous permettent de r\u00e9cup\u00e9rer les donn\u00e9es sans bloquer le thread. Avec cette version, <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/pull\/2628\" target=\"_blank\" rel=\"noopener\">nous arr\u00eatons la prise en charge<\/a> des fonctions <code>awaitSingleOr*<\/code> dans les <code>Publisher<\/code> arbitraires et avons sp\u00e9cialis\u00e9 certaines fonctions <code>await*<\/code> pour <code>Mono<\/code> et <code>Maybe<\/code>.<\/p>\n\n\n\n<p><code>Mono<\/code> produit au plus une valeur, le dernier \u00e9l\u00e9ment est donc le m\u00eame que le premier. Dans ce cas, la s\u00e9mantique de suppression des \u00e9l\u00e9ments restants est \u00e9galement inutile. Par cons\u00e9quent, la prise en charge de <code>Mono.awaitFirst()<\/code> et <code>Mono.awaitLast()<\/code> est interrompue et remplac\u00e9e par celle de <code>Mono.awaitSingle()<\/code>.<\/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 class=\"wp-block-heading\" id=\"use-coroutines-1-5-0\">Commencez \u00e0 utiliser kotlinx.coroutines 1.5.0&nbsp;! <\/h1>\n\n\n\n<p>Cette nouvelle version apporte un nombre impressionnant de nouveaut\u00e9s. Le nouveau sch\u00e9ma de nommage mis au point lors du perfectionnement de l&#8217;API de canaux est l&#8217;une des r\u00e9alisations les plus remarquable de l&#8217;\u00e9quipe. Parall\u00e8lement, nous nous effor\u00e7ons de rendre l&#8217;API des coroutines aussi simple et intuitive que possible.<\/p>\n\n\n\n<p>Pour commencer \u00e0 utiliser la nouvelle version de Kotlin Coroutines, il suffit de mettre \u00e0 jour le contenu de votre fichier build.gradle.kts. Assurez-vous d&#8217;abord de disposer de la derni\u00e8re version du plugin Kotlin Gradle&nbsp;:<\/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>Puis, mettez \u00e0 jour les versions des d\u00e9pendances, y compris les biblioth\u00e8ques avec des int\u00e9grations sp\u00e9cifiques pour les 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 class=\"wp-block-heading\">Plus de vid\u00e9os et d&#8217;articles<\/h1>\n\n\n\n<ul><li><a href=\"https:\/\/youtu.be\/EVLnWOcR0is\" class=\"ek-link\" target=\"_blank\" rel=\"noopener\">Vid\u00e9o sur Kotlin Coroutines 1.5.0<\/a><\/li><li><a href=\"https:\/\/kotlinlang.org\/docs\/coroutines-guide.html\" target=\"_blank\" rel=\"noopener\">Guide des coroutines<\/a><\/li><li><a href=\"https:\/\/kotlin.github.io\/kotlinx.coroutines\/\" target=\"_blank\" rel=\"noopener\">Documentation de l&#8217;API<\/a><\/li><li><a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\" target=\"_blank\" rel=\"noopener\">D\u00e9p\u00f4t GitHub de 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\">Article de blog sur Coroutines 1.4.0<\/a><\/li><li><a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2021\/05\/kotlin-1-5-0-released\/\" class=\"ek-link\">Article de blog sur Kotlin 1.5.0<\/a><\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">En cas de probl\u00e8me<\/h3>\n\n\n\n<ul><li>Signalez-nous tout probl\u00e8me sur <a href=\"https:\/\/github.com\/Kotlin\/kotlinx.coroutines\/issues\" target=\"_blank\" rel=\"noopener\">GitHub<\/a>.<\/li><li>Vous pouvez \u00e9galement demander de l&#8217;aide dans le canal Slack #coroutines de Kotlin (<a href=\"https:\/\/surveys.jetbrains.com\/s3\/kotlin-slack-sign-up\" target=\"_blank\" rel=\"noopener\">obtenir une invitation<\/a>).<\/li><\/ul>\n\n\n<p><em>Auteurs de l&#8217;article original en anglais : <a href=\"https:\/\/blog.jetbrains.com\/author\/antonarhipov\/\" target=\"_blank\" rel=\"noopener\">Anton Arhipov<\/a> et <a href=\"https:\/\/blog.jetbrains.com\/author\/svetlana-isakova\/\" target=\"_blank\" rel=\"noopener\">Svetlana Isakova<\/a><\/em><\/p>\n<p><em><\/em><\/p>\n<p><em><\/em><\/p><em>\n<p><em><\/em><\/p>\n<\/em><p><em><\/em><\/p>\n<p><em><\/em><\/p>","protected":false},"author":813,"featured_media":142993,"comment_status":"closed","ping_status":"closed","template":"","categories":[909],"tags":[5319,671],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/kotlin\/152942"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/kotlin"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/types\/kotlin"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/users\/813"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/comments?post=152942"}],"version-history":[{"count":11,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/kotlin\/152942\/revisions"}],"predecessor-version":[{"id":153001,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/kotlin\/152942\/revisions\/153001"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media\/142993"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media?parent=152942"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/categories?post=152942"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/tags?post=152942"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/cross-post-tag?post=152942"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}