Sobes.tech
Middle

Come configurare e usare le coroutine nei progetti Kotlin?

sobes.tech AI

Risposta dell'AI

Per configurare le coroutine, è necessario aggiungere la dipendenza corrispondente nel file build.gradle.

// build.gradle (modulo app)
dependencies {
    //... altre dipendenze
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") // Per JVM, Android, JS, Native
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") // Per Android (se necessario Dispatchers.Main)
}

L'uso delle coroutine include:

  1. Scope: Definire l'ambito in cui le coroutine verranno eseguite e gestire il loro ciclo di vita.

    • GlobalScope: Ambito di tutta l'applicazione, usare con cautela, poiché può causare perdite di memoria.
    • CoroutineScope: Creato esplicitamente, permette di controllare l'annullamento delle coroutine.
    • Ambiti predefiniti nei framework (ad esempio, lifecycleScope in Android ViewModel).
    // Esempio con CoroutineScope
    val myScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) // Creazione di uno Scope con Dispatcher e Job
    
    fun performBackgroundTask() {
        myScope.launch { // Avvia una coroutine
            // Processo di calcolo lungo
        }
    }
    
    fun cancelAllTasks() {
        myScope.cancel() // Annulla tutte le coroutine nello Scope
    }
    
  2. Builder: Funzioni per avviare coroutine.

    • launch: Avvia una coroutine che non restituisce risultato esplicito (Job).
    • async: Avvia una coroutine che restituisce risultato sotto forma di Deferred.
    import kotlinx.coroutines.*
    
    suspend fun fetchData() = coroutineScope { // coroutineScope - builder per la concorrenza strutturata
        val data1 = async { networkCall1() } // async restituisce un Deferred
        val data2 = async { networkCall2() }
    
        // ... elaborazione dei dati dopo averli ricevuti
        data1.await() // Ottenere risultato da Deferred, sospende la coroutine fino al completamento
        data2.await()
    }
    
    suspend fun networkCall1(): String {
        delay(1000) // Simulazione di richiesta di rete
        return "Data 1"
    }
    
    suspend fun networkCall2(): String {
        delay(1500)
        return "Data 2"
    }
    
    fun startFetching() = runBlocking { // runBlocking - builder per bloccare il thread corrente (per main o test)
        launch { // launch restituisce un Job
            println("Fetching data...")
            fetchData()
            println("Data fetched!")
        }
    }
    
  3. Dispatchers: Determinano su quale thread o pool di thread verrà eseguita la coroutine.

    • Dispatchers.Default: Per compiti intensivi in CPU.
    • Dispatchers.IO: Per operazioni di I/O bloccanti (file, rete).
    • Dispatchers.Main: Thread principale (solo per Android e Swing/JavaFX).
    • Dispatchers.Unconfined: Avvia la coroutine nel thread chiamante, si sospende e poi si riprende nel thread che la ha ripresa.
    import kotlinx.coroutines.*
    
    fun simpleTask() {
        GlobalScope.launch(Dispatchers.IO) { // Specificare il dispatcher
            // Eseguire operazione di I/O
            println("Esecuzione sul thread: ${Thread.currentThread().name}")
        }
    }
    
    fun main() = runBlocking {
        launch(Dispatchers.Default) {
            println("Thread di default: ${Thread.currentThread().name}")
        }
        launch(Dispatchers.Unconfined) {
             println("Thread non confinato 1: ${Thread.currentThread().name}")
             delay(100) // Sospensione
             println("Thread non confinato 2: ${Thread.currentThread().name}") // Ripresa in altro thread (possibilmente)
        }
        delay(200) // Attendere il completamento delle coroutine
    }
    
  4. Funzioni di sospensione: Funzioni contrassegnate con suspend, che possono essere sospese e riprese senza bloccare il thread. Possono essere chiamate solo da altre funzioni di sospensione o blocchi di coroutine.

  5. Gestione di cancellazione ed eccezioni: Le coroutine supportano cancellazione strutturata e propagazione delle eccezioni.

    • Cancellazione:
      import kotlinx.coroutines.*
      
      fun cancellableTask() = GlobalScope.launch {
          try {
              repeat(1000) { i ->
                  println("Lavorando $i ...")
                  delay(100) // funzione di sospensione che rispetta la cancellazione
              }
          } catch (e: CancellationException) {
              println("Tarefa cancellata!")
          } finally {
              println("Pulizia...")
          }
      }
      
      fun main() = runBlocking {
          val job = cancellableTask()
          delay(500) // Attendere un po'
          job.cancelAndJoin() // Annulla e aspetta la fine
      }
      
    • Eccezioni: In caso di launch, un'eccezione non catturata causerà il crash dell'applicazione (su Android, di default). In caso di async, l'eccezione verrà lanciata al momento della chiamata di await(). Per un controllo più granulare, si usano CoroutineExceptionHandler o SupervisorJob.
    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val handler = CoroutineExceptionHandler { _, exception ->
            println("Eccezione catturata: $exception")
        }
    
        val job = GlobalScope.launch(handler) { // Applicare il gestore
            throw IllegalStateException("Qualcosa è andato storto")
        }
        job.join() // Attendere il completamento
    }