Coroutines Kotlin

Un’introduzione alle coroutine di Kotlin

Per evitare il degrado o l’interruzione della reattività dell’interfaccia utente, è importante che le attività che richiedono tempo non blocchino l’esecuzione del thread principale. Un’opzione è eseguire tali attività su un thread in background, lasciando così il thread principale per continuare a gestire l’interfaccia utente. Ciò può essere ottenuto direttamente utilizzando i gestori di thread o facendo uso della classe AsyncTask.
Sebbene i gestori di thread e AsyncTask forniscano un modo per eseguire attività su thread separati, può essere dispendioso in termini di tempo per l’implementazione e confusione per leggere e mantenere il codice associato in un progetto di app. Questo approccio non è inoltre la soluzione più efficiente quando un’app richiede un numero elevato di thread.
Fortunatamente, Kotlin offre un’alternativa leggera sotto forma di Coroutine. Introdurremo i concetti di base delle Coroutine, inclusa la terminologia come dispatcher, tipi di coroutine, funzioni di sospensione, costruttori coroutine e concorrenza strutturata. Inoltre i concetti di base della comunicazione basata sul canale tra le coroutine.

Cosa sono le coroutine?

Le coroutine sono blocchi di codice che vengono eseguiti in modo asincrono senza bloccare il thread dal quale vengono avviati. Le coroutine possono essere implementate senza doversi preoccupare della realizzazione di implementazioni complesse di AsyncTask o della gestione diretta di più thread. A causa del modo in cui sono implementate, le coroutine sono molto più efficienti e richiedono meno risorse rispetto all’utilizzo delle tradizionali opzioni multi-threading. Le coroutine rendono anche il codice che è molto più facile da scrivere, comprendere e mantenere poiché consente di scrivere il codice in sequenza senza dover scrivere callback per gestire eventi e risultati relativi al thread.
Sebbene un’aggiunta relativamente recente a Kotlin, non vi è nulla di nuovo o di innovativo nelle coroutine. Le coroutine in una forma o nell’altra esistono nei linguaggi di programmazione dagli anni ’60 e si basano su un modello noto come Communicating Sequential Processes (CSP). In effetti, Kotlin usa ancora il multi-threading dietro le quinte, anche se lo fa in modo molto efficiente.

Analizziamo meglio il problema

Un problema con i thread è che sono una risorsa limitata costosa in termini di capacità della CPU e sovraccarico del sistema. Sullo sfondo, molto lavoro è coinvolto nella creazione, pianificazione e distruzione di un thread. Sebbene le CPU moderne siano in grado di eseguire un numero elevato di thread, il numero effettivo di thread che possono essere eseguiti in parallelo in qualsiasi momento è limitato dal numero di core della CPU (anche se le CPU più recenti hanno 8 core, la maggior parte dei dispositivi Android contiene CPU con 4 core). Quando sono necessari più thread rispetto ai core della CPU, il sistema deve eseguire la pianificazione dei thread per decidere come condividere l’esecuzione di questi thread tra i core disponibili.
Per evitare queste spese generali, anziché avviare un nuovo thread per ogni coroutine e poi distruggerlo quando il coroutine esce, Kotlin mantiene un pool di thread attivi e gestisce il modo in cui i coroutine sono assegnati a quei thread. Quando una coroutine attiva viene sospesa, viene salvata dal runtime di Kotlin e un’altra coroutine riprende a prendere il suo posto. Quando la coroutine viene ripresa, viene semplicemente ripristinata in un thread non occupato esistente all’interno del pool per continuare l’esecuzione fino a quando non viene completata o sospesa. Utilizzando questo approccio, un numero limitato di thread viene utilizzato in modo efficiente per eseguire attività asincrone con il potenziale per eseguire un numero elevato di attività simultanee senza le degenerazioni delle prestazioni intrinseche che si verificherebbero utilizzando il multi-threading standard.

Coroutine Scope

Tutte le coroutine devono essere eseguite in un ambito specifico che consenta loro di essere gestite come gruppi anziché come singole coroutine. Ciò è particolarmente importante quando si annulla e si puliscono le coroutine, ad esempio quando un frammento o attività viene distrutto e si assicura che le coroutine non “perdano” (in altre parole continuano a funzionare in background quando non sono più necessarie all’app). Assegnando le coroutine a un ambito, ad esempio, possono essere tutte cancellate in blocco quando non sono più necessarie.
Kotlin offre alcuni ambiti integrati e l’opzione per creare ambiti personalizzati utilizzando la classe CoroutineScope. Gli ambiti integrati possono essere riassunti come segue:
GlobalScope: GlobalScope viene utilizzato per avviare coroutine di livello superiore che sono legate all’intero ciclo di vita dell’applicazione. Poiché questo ha il potenziale per le coroutine in questo ambito di continuare a funzionare quando non necessario (ad esempio quando si esce da un’attività), l’uso di questo ambito non è raccomandato per l’uso in applicazioni Android. Le coroutine in esecuzione in GlobalScope sono considerate in uso con concorrenza non strutturata.
ViewModelScope – Fornito specificatamente per l’uso nelle istanze ViewModel quando si utilizza il componente ViewModel dell’architettura Jetpack. Le coroutine avviate in questo ambito dall’interno di un’istanza ViewModel vengono automaticamente annullate dal sistema di runtime di Kotlin quando viene distrutta l’istanza ViewModel corrispondente.
Per tutti gli altri requisiti. Molto probabilmente verrà utilizzato un ambito personalizzato. Il codice seguente, ad esempio, crea un ambito personalizzato denominato mia_Coroutines_Scope:
private val mia_Coroutines_Scope = CoroutineScope (Dispatchers.Main)
CoroutineScope dichiara il dispatcher che verrà utilizzato per eseguire le coroutine (sebbene ciò possa essere ignorato) e deve essere referenziato ogni volta che una coroutine viene avviata se deve essere inclusa nell’ambito. Tutte le coroutine in esecuzione in un ambito possono essere annullate tramite una chiamata al metodo cancel () dell’istanza ambito:

mia_Coroutines_Scope.cancel ()

Funzioni di sospensione

Una funzione di sospensione è un tipo speciale di funzione di Kotlin che contiene il codice di un coroutine. Viene dichiarato utilizzando la parola chiave suspend di Kotlin che indica a Kotlin che la funzione può essere messa in pausa e ripresa in seguito, consentendo l’esecuzione di calcoli di lunga durata senza bloccare il thread principale.
Di seguito è riportato un esempio di funzione di sospensione:
suspend fun mioLentoLavoro () {
// Esegui attività di lunga durata qui
}

Coroutine Dispatcher

Kotlin mantiene i thread per diversi tipi di attività asincrona e, all’avvio di una coroutine, sarà necessario selezionare il dispatcher appropriato tra le seguenti opzioni:
Dispatchers.Main – Esegue la procedura di routine sul thread principale e adatta per le procedure di coroutine che devono apportare modifiche all’interfaccia utente e come opzione per scopi generali per l’esecuzione di attività leggere.
Dispatchers.IO – Consigliato per coroutine che eseguono operazioni di rete, su disco o su database.
• Dispatchers.Default – Destinato a compiti intensivi della CPU come l’ordinamento dei dati o l’esecuzione di calcoli complessi.

Il dispatcher è responsabile dell’assegnazione delle coroutine ai thread appropriati e della sospensione e ripresa del coroutine durante il suo ciclo di vita. Oltre ai dispatcher predefiniti, è anche possibile creare dispatcher per i propri pool di thread personalizzati.

Costruttori di Coroutine

I costruttori di coroutine riuniscono tutti i componenti coperti finora e lanciano effettivamente i coroutine in modo che inizino a eseguire. A tale scopo, Kotlin fornisce i seguenti sei costruttori:
• launch – Avvia una routine senza bloccare il thread corrente e non restituisce un risultato al chiamante. Utilizzare questo builder quando si chiama una funzione di sospensione dall’interno di una funzione tradizionale e quando non è necessario gestire i risultati della coroutine (a volte denominati coroutine “fuoco e dimentica”).
• asincrono: avvia una routine e consente al chiamante di attendere un risultato utilizzando la funzione await () senza bloccare il thread corrente. Utilizzare asincrono quando si hanno più coroutine che devono funzionare in parallelo. Il builder asincrono può essere utilizzato solo da un’altra funzione di sospensione.
• withContext: consente di avviare una coroutine in un contesto diverso da quello utilizzato dalla coroutine padre. Una coroutine in esecuzione usando il contesto Main potrebbe, ad esempio, lanciare una coroutine figlio nel contesto Default usando questo builder. Il builder withContext fornisce anche un’utile alternativa all’asincrono quando si restituiscono risultati da una routine.
• coroutineScope – Il builder coroutineScope è ideale per le situazioni in cui una funzione di sospensione avvia più coroutine che verranno eseguite in parallelo e in cui è necessario eseguire alcune azioni solo al completamento di tutte le coroutine. Se tali coroutine vengono avviate utilizzando il builder coroutineScope, la funzione di chiamata non ritorna fino al completamento di tutte le coroutine figlio. Quando si utilizza coroutineScope, un guasto in una qualsiasi delle coroutine comporterà la cancellazione di tutte le altre coroutine.
• supervisorScope – Simile al coroutineScope descritto sopra, con l’eccezione che un fallimento in un bambino non comporta la cancellazione delle altre coroutine.
• runBlocking – Avvia un coroutine e blocca il thread corrente fino al completamento del coroutine. Questo è in genere l’esatto contrario di ciò che si desidera dalle coroutine ma utile per testare il codice e quando si integrano il codice e le librerie legacy. Altrimenti da evitare.

Stato della coroutines

Ogni chiamata a un costruttore di coroutine come launch o async restituisce un’istanza Job che, a sua volta, può essere utilizzata per tenere traccia e gestire il ciclo di vita del coroutine corrispondente. Le successive chiamate del builder dalla coroutine creano nuove istanze di Job che diventeranno figli del Job genitore immediato formando un albero di relazioni genitore-figlio in cui la cancellazione di un Job genitore annullerà in modo ricorsivo tutti i suoi figli. L’annullamento di un figlio non comporta tuttavia l’annullamento del genitore, sebbene un’eccezione non rilevata all’interno di un figlio creata utilizzando il generatore di avvio possa comportare la cancellazione del genitore (ciò non è il caso dei bambini creati utilizzando il costruttore asincrono che incapsula l’eccezione in il risultato è tornato al genitore).
Lo stato di una coroutine può essere identificato accedendo alle proprietà isActive, isCompleted e isCancelled dell’oggetto Job associato. Oltre a queste proprietà, sono disponibili anche diversi metodi su un’istanza Job. Un lavoro e tutti i suoi figli possono, ad esempio, essere annullati chiamando il metodo cancel () dell’oggetto Job, mentre una chiamata al metodo cancelChildren () annullerà tutte le coroutine figlio.
Il metodo join () può essere chiamato per sospendere la coroutine associata al lavoro fino al completamento di tutti i lavori figlio. Per eseguire questa attività e annullare il processo una volta completati tutti i processi figlio, è sufficiente chiamare il metodo cancelAndJoin ().

Questa struttura gerarchica del lavoro, insieme agli ambiti coroutine, costituisce la base della concorrenza strutturata, il cui obiettivo è garantire che le coroutine non durino più a lungo di quanto sono necessarie senza la necessità di conservare manualmente i riferimenti a ciascun coroutine.

Coroutine – Sospensione e ripresa

Per comprendere meglio la sospensione di coroutine, aiuta a vedere alcuni esempi di coroutine in azione. Per cominciare, supponiamo che una semplice app Android contenga un pulsante che, quando viene cliccato, chiama una funzione chiamata startTask (). È responsabilità di questa funzione chiamare una funzione di sospensione denominata performSlowTask () utilizzando il dispatcher coroutine principale. Il codice per questo potrebbe essere il seguente:
private val mia_Coroutines_Scope = CoroutineScope (Dispatchers.Main)
fun inizioLavoro (view: View) {
mia_Coroutines_Scope.launch (Dispatchers.Main) {
avvioDiUnLavoroLungoELento ()
}
}
Nel codice precedente, un ambito personalizzato viene dichiarato e fatto riferimento nella chiamata al generatore di avvio che, a sua volta, chiama la funzione di sospensione performSlowTask (). Si noti che poiché startTask () non è una funzione di sospensione, la coroutine deve essere avviata utilizzando il generatore di avvio anziché il generatore asincrono.
Successivamente, possiamo dichiarare la funzione di sospensione performSlowTask () come segue:
suspend fun avvioDiUnLavoroLungoELento () {
Log.i (TAG, “avvioDiUnLavoroLungoELento prima”)
delay (5_000) // simula attività di lunga durata
Log.i (TAG, “avvioDiUnLavoroLungoELento dopo”)
}

Restituzione dei risultati da un Coroutine

L’esempio precedente ha eseguito una funzione di sospensione come coroutine ma non ha dimostrato come restituire risultati. Supponiamo, tuttavia, che la funzione avvioDiUnLavoroLungoELento () sia richiesta per restituire un valore di stringa che deve essere visualizzato all’utente tramite un oggetto TextView.
Per fare ciò, è necessario riscrivere la funzione di sospensione per restituire un oggetto differito. Un oggetto differito è essenzialmente un impegno a fornire un valore ad un certo punto in futuro. Chiamando la funzione await () sull’oggetto Deferred(Differito), il runtime di Kotlin fornirà il valore quando viene restituito dal coroutine. Il codice nella nostra funzione inizioLavoro () potrebbe quindi essere riscritto come segue:
fun inizioLavoro (view: View) {
coroutineScope.launch (Dispatchers.Main) {
statusText.text =avvioDiUnLavoroLungoELento(). await ()
}
}
Il problema ora è che dobbiamo usare il generatore di avvio per avviare la routine poiché inizioLavoro () non è una funzione di sospensione. Come indicato in precedenza in questo capitolo, è possibile restituire risultati solo quando si utilizza il builder asincrono. Per ovviare a questo, dobbiamo adattare la funzione di sospensione in modo da utilizzare il builder asincrono per avviare un altro programma che restituisce un risultato differito:
suspend fun avvioDiUnLavoroLungoELento (): Deferred =
coroutineScope.async (Dispatchers.Default) {
Log.i (TAG, “avvioDiUnLavoroLungoELento prima”)
ritardo (5_000)
Log.i (TAG, “avvioDiUnLavoroLungoELento dopo”)
return @ async “Finito”
}
Ora quando l’app viene eseguita, la stringa del risultato “Finished” verrà visualizzata sull’oggetto TextView al completamento della routine di avvioDiUnLavoroLungoELento (). Ancora una volta, l’attesa per il risultato avrà luogo in background senza bloccare il thread principale.

Utilizzo con withContext

Come abbiamo visto, le coroutine vengono lanciate in un ambito specifico e utilizzando un dispatcher specifico. Per impostazione predefinita, qualsiasi coroutine figlio erediterà
lo stesso dispatcher utilizzato dal genitore. Si consideri il seguente codice progettato per chiamare più funzioni all’interno di una funzione di sospensione:
fun startTask (view: View) {
coroutineScope.launch (Dispatchers.Main) {
performTasks ()
}
}
suspend fun avvioDiUnLavoroLungoELento () {
avvioDiUnLavoroLungoELento1 ()
avvioDiUnLavoroLungoELento2 ()
avvioDiUnLavoroLungoELento3 ()
}
suspend fun avvioDiUnLavoroLungoELento1 () {
Log.i (TAG, “Task 1 $ {Thread.currentThread (). Name}”)
}
suspend fun avvioDiUnLavoroLungoELento2 () {
Log.i (TAG, “Task 2 $ {Thread.currentThread (). Name}”)
}
suspend fun avvioDiUnLavoroLungoELent3 () {
Log.i (TAG, “Task 3 $ {Thread.currentThread (). Name}”)
}
Poiché la funzione performTasks () è stata avviata utilizzando il dispatcher principale, tutte e tre le funzioni verranno impostate sul thread principale per impostazione predefinita. Per dimostrarlo, le funzioni sono state scritte per generare il nome del thread in cui sono in esecuzione. Al momento dell’esecuzione, il pannello Logcat conterrà il seguente output:
Attività 1 principale
Attività 2 principale
Attività 3 principale
Immagina, tuttavia, che la funzione avvioDiUnLavoroLungoELento2 () esegua alcune operazioni ad alta intensità di rete più adatte al dispatcher IO. Ciò può essere facilmente ottenuto utilizzando il launcher withContext che consente di modificare il contesto di una coroutine pur rimanendo nello stesso ambito coroutine. La seguente modifica imposta la routine avvioDiUnLavoroLungoELento2 () su un thread IO:
suspend fun avvioDiUnLavoroLungoELento () {
avvioDiUnLavoroLungoELento1 ()
withContext (Dispatchers.IO) {avvioDiUnLavoroLungoELento2 ()}
avvioDiUnLavoroLungoELento3 ()
}
Quando eseguito, l’output leggerà come segue indicando che la procedura 2 del task non è più sul thread principale:
Attività 1 principale
Attività 2 DefaultDispatcher-worker-1
Attività 3 principale
Il builder withContext fornisce anche un’interessante alternativa all’utilizzo del builder asincrono e della chiamata Deferred object await () quando si restituisce un risultato. Utilizzando withContext, può essere il codice della sezione precedente
riscritto come segue:
fun inizioLavoro (view: View) {
coroutineScope.launch (Dispatchers.Main) {
statusText.text = avvioDiUnLavoroLungoELento ()
}
}
suspend fun avvioDiUnLavoroLungoELento (): String =
withContext (Dispatchers.Main) {
Log.i (TAG, “avvioDiUnLavoroLungoELento prima”)
ritardo (5_000)
Log.i (TAG, “avvioDiUnLavoroLungoELento dopo”)

return @ withContext “Finito”
}

Un canale di comunicazione per le Coroutine

I canali forniscono un modo semplice per implementare la comunicazione tra coroutine, inclusi flussi di dati. Nella forma più semplice ciò comporta la creazione di un’istanza di canale e la chiamata al metodo send () per inviare i dati. Una volta inviati, i dati trasmessi possono essere ricevuti in un’altra coroutine tramite una chiamata al metodo di ricezione () della stessa istanza di canale.
Il seguente codice, ad esempio, passa sei numeri interi da una routine a un’altra:
import kotlinx.coroutines.channels. *
.
.
val channel = Channel ()
suspend fun channelDemo () {
coroutineScope.launch (Dispatchers.Main) {performTask1 ()}
coroutineScope.launch (Dispatchers.Main) {performTask2 ()}
}
suspend fun performTask1 () {
(1..6) .per ogni {
channel.send (it)
}
}
suspend fun performTask2 () {
ripeti (6) {
Log.d (TAG, “Ricevuto: $ {channel.recieve ()}”)
}
}
Quando eseguito, verrà generato il seguente output logcat:
Ricevuto: 1
Ricevuto: 2
Ricevuto: 3
Ricevuto: 4
Ricevuto: 5
Ricevuto: 6
65.12 Sommario
Le coroutine di Kotlin offrono un approccio più semplice ed efficiente all’esecuzione di compiti asincroni rispetto a quello offerto dal multi-threading tradizionale. Le coroutine consentono alle attività asincrone di essere implementate in modo strutturato senza la necessità di implementare i callback associati alle tipiche attività basate su thread.

Un esempio Android Kotlin Coroutines

Creiamo un’app di esempio che avvia migliaia di coroutine con il semplice tocco di un pulsante.

La versione corrente di Android Studio non include automaticamente il supporto per le coroutine nei progetti appena creati. Prima di procedere, quindi, modifica il file Gradle Scripts -> build.gradle (Modulo: app) e
aggiungi le seguenti righe alla sezione delle dipendenze (notando, come sempre, che potrebbero essere disponibili versioni più recenti delle librerie):
dipendenze {
.
.
implementazione “org.jetbrains.kotlinx: kotlinx-coroutines-core: versione attuale”
implementazione ‘org.jetbrains.kotlinx: kotlinx-coroutines-android:versione attuale’
.
.
}

L’interfaccia utente sarà composta da un pulsante per avviare le coroutine insieme a una Seekbar che segna quante coroutine devono essere lanciate in modo asincrono ogni volta che si fa clic sul pulsante. Man mano che le coroutine vengono eseguite, un TextView si aggiorna quando iniziano e finiscono le singole coroutine.
Inizia caricando il file di layout activity_main.xml e aggiungi gli oggetti Button, TextView e SeekBar

Tenete premuto Maiusc e fate clic sui quattro oggetti in modo che tutti siano selezionati, fate clic con il pulsante destro del mouse su TextView più in alto e selezionate l’opzione di menu Centra -> Orizzontalmente. Fai di nuovo clic con il pulsante destro del mouse, questa volta selezionando l’opzione Catene -> Crea catena verticale.
Seleziona la SeekBar e modifica la proprietà layout_width su 0dp (match_constraints) prima di aggiungere un margine di 24dp sui lati sinistro e destro.

Modifica l’attributo onClick affinché il pulsante chiami un metodo chiamato launchCoroutines e cambi gli ID del TextView più in alto, del SeekBar e del TextView in basso rispettivamente per countText, seekBar e statusText. Infine, modifica il testo sul pulsante per leggere “Launch Coroutines” ed estrarre il testo in una risorsa stringa.

Implementazione di SeekBar

SeekBar controlla il numero di coroutine asincrone, che vanno da 1 a 2000, che vengono lanciate ogni volta che si fa clic sul pulsante. Rimanendo all’interno del file activity_main.xml, selezionare SeekBar e utilizzare la finestra dello strumento Attributi per modificare la proprietà max in 2000. Successivamente, modificare il file MainActivity.kt, aggiungere una variabile in cui memorizzare le impostazioni correnti del dispositivo di scorrimento e modificare onCreate ( ) per aggiungere un listener SeekBar:
.
.
import android.widget.SeekBar
import kotlinx.android.synthetic.main.activity_main. *
.
.
class MainActivity: AppCompatActivity () {

conteggio var privato: Int = 1

override fun onCreate (savedInstanceState: Bundle?) {
super.onCreate (savedInstanceState)
setContentView (R.layout.activity_main)

seekBar? .setOnSeekBarChangeListener (oggetto:
SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged (seek: SeekBar,
progress: Int, fromUser: Boolean) {
conteggio = progresso
countText.text = “$ {count} coroutine”
}

override fun onStartTrackingTouch (seek: SeekBar) {
}

override fun onStopTrackingTouch (seek: SeekBar) {
}
})
}
Quando la barra di ricerca scorre, il valore corrente verrà memorizzato nella variabile count e visualizzato nella vista countText.

Aggiunta della funzione di sospensione

Quando l’utente tocca il pulsante, l’app dovrà avviare il numero di coroutine selezionate nella SeekBar. Il metodo onClick launchCoroutines () raggiungerà questo obiettivo utilizzando il builder di avvio coroutine per eseguire una funzione di sospensione. Poiché la funzione di sospensione restituirà una stringa di stato da visualizzare sull’oggetto statusText TextView, dovrà essere implementata utilizzando il builder asincrono. Tutte queste azioni dovranno essere eseguite in un ambito coroutine che deve anche essere dichiarato
All’interno del file MainActivity.kt apportare le seguenti modifiche:
.
.
import kotlinx.coroutines. *
.
.
class MainActivity: AppCompatActivity () {
private val coroutineScope = CoroutineScope (Dispatchers.Main)
.
.
suspend fun performTask (tasknumber: Int): Deferred =
coroutineScope.async (Dispatchers.Main) {
ritardo (5_000)
return @ async “Coroutine finita $ {tasknumber}”
}
.
.
}
Dato che la funzione esegue solo una piccola attività e comporta modifiche all’interfaccia utente, la coroutine viene eseguita utilizzando il dispatcher principale. Viene passato il numero progressivo della coroutine da avviare, ritarda per 5 secondi e quindi restituisce una stringa che indica che la coroutine numerata è terminata.

Implementazione del metodo launchCoroutines

L’attività finale prima di testare l’app è aggiungere il metodo launchCoroutines () che viene chiamato quando si fa clic sull’oggetto Button. Questo metodo deve essere aggiunto al file MainActivity.kt come segue:

Compilare ed eseguire l’app su un dispositivo o emulatore e spostare SeekBar su un numero basso (ad esempio 10) prima di toccare il pulsante di avvio. Il testo dello stato si aggiorna ogni volta che viene lanciata una procedura periodica fino al raggiungimento del massimo. Dopo che ogni coroutine completa il ritardo di 5 secondi, il testo dello stato si aggiornerà fino al completamento di tutti i 10 (in pratica questi aggiornamenti di stato avverranno così rapidamente che sarà difficile vedere i cambiamenti di stato).
Ripeti il ??processo con SeekBar impostato su 2000, questa volta facendo scorrere avanti e indietro la barra di ricerca mentre le coroutine corrono per verificare che il thread principale sia ancora in esecuzione e non sia stato bloccato.
Infine, con il pannello Logcat visualizzato, impostare SeekBar su 2000 e fare clic ripetutamente sul pulsante di avvio. Dopo circa 15 clic, il pannello Logcat inizierà a visualizzare messaggi simili ai seguenti:
I / Coreografo: ho saltato 52 fotogrammi! L’applicazione potrebbe fare troppo lavoro sul suo thread principale.
Sebbene l’app continui a funzionare, chiaramente il volume di coroutine in esecuzione all’interno dell’app sta iniziando a sovraccaricare il thread principale. Il fatto che ciò si verifichi solo quando decine di migliaia di coroutine vengono eseguite contemporaneamente è una testimonianza dell’efficienza delle coroutine di Kotlin. Quando questo messaggio inizia a comparire nelle tue app, tuttavia, potrebbe essere un segno che troppe coroutine sono in esecuzione o che il carico di lavoro asincrono in esecuzione è troppo pesante per il principale
filo. Stando così le cose, potrebbe essere necessario utilizzare un dispatcher diverso, magari utilizzando il builder withContext.

.
import android.view.View
.
.
fun launchCoroutines (view: View) {

(1..count) .Per ogni {
statusText.text = “Coroutine avviata $ {it}”
coroutineScope.launch (Dispatchers.Main) {
statusText.text = performTask (it) .await ()
}
}
}
.
.
Il metodo implementa un ciclo per avviare il numero richiesto di coroutine e aggiorna lo stato TextView ogni volta che un risultato viene restituito da una coroutine completata tramite una chiamata al metodo waitit ().