Sobes.tech
Middle

Jak konfigurować i używać korutyn w projektach Kotlin?

sobes.tech AI

Odpowiedź od AI

Aby skonfigurować korutyny, należy dodać odpowiednią zależność do pliku build.gradle.

// build.gradle (moduł aplikacji)
dependencies {
    //... inne zależności
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") // Dla JVM, Android, JS, Native
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") // Dla Android (jeśli potrzebny Dispatchers.Main)
}

Użycie korutyn obejmuje:

  1. Zakres życia (Scope): Określenie zakresu, w którym korutyny będą wykonywane i zarządzać swoim cyklem życia.

    • GlobalScope: Zakres życia całej aplikacji, używać ostrożnie, ponieważ może prowadzić do wycieków pamięci.
    • CoroutineScope: Tworzony jawnie, pozwala kontrolować anulowanie korutyn.
    • Predefiniowane zakresy w frameworkach (np. lifecycleScope w Android ViewModel).
    // Przykład z CoroutineScope
    val myScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) // Tworzenie zakresu z Dispatcher i Job
    
    fun performBackgroundTask() {
        myScope.launch { // Uruchomienie korutyny
            // Długi proces obliczeniowy
        }
    }
    
    fun cancelAllTasks() {
        myScope.cancel() // Anulowanie wszystkich korutyn w zakresie
    }
    
  2. Twórcy korutyn (Builders): Funkcje do uruchamiania korutyn.

    • launch: Uruchamia korutynę, która nie zwraca wyniku jawnie (zwraca Job).
    • async: Uruchamia korutynę, która zwraca wynik w postaci Deferred.
    import kotlinx.coroutines.*
    
    suspend fun fetchData() = coroutineScope { // coroutineScope - builder dla strukturalnej konkurencji
        val data1 = async { networkCall1() } // async zwraca Deferred
        val data2 = async { networkCall2() }
    
        // ... przetwarzanie danych po ich otrzymaniu
        data1.await() // Pobranie wyniku z Deferred, powoduje uśpienie korutyny do zakończenia
        data2.await()
    }
    
    suspend fun networkCall1(): String {
        delay(1000) // Symulacja zapytania sieciowego
        return "Data 1"
    }
    
    suspend fun networkCall2(): String {
        delay(1500)
        return "Data 2"
    }
    
    fun startFetching() = runBlocking { // runBlocking - builder do blokowania bieżącego wątku (np. main lub testy)
        launch { // launch zwraca Job
            println("Fetching data...")
            fetchData()
            println("Data fetched!")
        }
    }
    
  3. Dispatcher (Dispatchers): Określają, na którym wątku lub puli wątków będzie wykonywana korutyna.

    • Dispatchers.Default: Dla zadań intensywnych CPU.
    • Dispatchers.IO: Dla blokujących operacji I/O (dostęp do plików, sieć).
    • Dispatchers.Main: Główny wątek (tylko dla Android i Swing/JavaFX).
    • Dispatchers.Unconfined: Uruchamia korutynę w wywołującym wątku, uśpiewa się, a następnie wznowi w wątku, który wznowił wykonanie.
    import kotlinx.coroutines.*
    
    fun simpleTask() {
        GlobalScope.launch(Dispatchers.IO) { // Wskazanie dispatcher
            // Wykonanie operacji I/O
            println("Running on thread: ${Thread.currentThread().name}")
        }
    }
    
    fun main() = runBlocking {
        launch(Dispatchers.Default) {
            println("Default thread: ${Thread.currentThread().name}")
        }
        launch(Dispatchers.Unconfined) {
             println("Unconfined thread 1: ${Thread.currentThread().name}")
             delay(100) // Uśpienie
             println("Unconfined thread 2: ${Thread.currentThread().name}") // Wznowienie w innym wątku (możliwe)
        }
        delay(200) // Czekanie na zakończenie korutyn
    }
    
  4. Funkcje zawieszające (Suspending functions): Funkcje oznaczone słowem kluczowym suspend, które mogą być zawieszone i wznowione bez blokowania wątku. Mogą być wywoływane tylko z innych funkcji zawieszających lub z bloku korutyny.

  5. Obsługa anulowania i wyjątków: Korutyny obsługują strukturalne anulowanie i propagację wyjątków.

    • Anulowanie:
      import kotlinx.coroutines.*
      
      fun cancellableTask() = GlobalScope.launch {
          try {
              repeat(1000) { i ->
                  println("Working $i ...")
                  delay(100) // funkcja zawieszająca, która respektuje anulowanie
              }
          } catch (e: CancellationException) {
              println("Task was cancelled!")
          } finally {
              println("Cleaning up...")
          }
      }
      
      fun main() = runBlocking {
          val job = cancellableTask()
          delay(500) // Czekanie trochę
          job.cancelAndJoin() // Anulowanie i czekanie na zakończenie
      }
      
    • Wyjątki: W przypadku launch, nieobsłużony wyjątek spowoduje upadek aplikacji (w Android domyślnie). W przypadku async, wyjątek zostanie rzucony podczas wywołania await(). Dla bardziej granularnej kontroli używa się CoroutineExceptionHandler lub SupervisorJob.
    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val handler = CoroutineExceptionHandler { _, exception ->
            println("Caught exception: $exception")
        }
    
        val job = GlobalScope.launch(handler) { // Zastosowanie obsługi wyjątków
            throw IllegalStateException("Something went wrong")
        }
        job.join() // Czekanie na zakończenie
    }