Sobes.tech
Middle

Jakie możliwości oferuje Kotlin do pracy z operacjami asynchronicznymi?

sobes.tech AI

Odpowiedź od AI

Kotlin zapewnia korutyny (coroutines) do upraszczania operacji asynchronicznych, oferując lekkie wątki, które nie blokują głównego wątku.

Kluczowe możliwości:

  • Funkcje zawieszenia: Funkcje ze słowem kluczowym suspend, które mogą być zawieszone i wznowione. Nie blokują wątku, na którym są wykonywane.
    suspend fun fetchData(): String {
        delay(1000) // Symulacja długiej operacji
        return "Dane otrzymane"
    }
    
  • Budowniczy korutyn: Funkcje używane do uruchamiania korutyn:
    • launch: Uruchamia nową korutynę i zwraca Job (nieblokujące).
      GlobalScope.launch {
          println("Uruchomiono korutynę")
      }
      
    • async: Uruchamia nową korutynę i zwraca Deferred<T> (blokuje przy wywołaniu .await()).
      val deferred = GlobalScope.async {
          fetchData()
      }
      // ...
      val result = deferred.await() // Oczekiwanie na wynik
      
    • runBlocking: Blokuje bieżący wątek do czasu zakończenia korutyny (używane w mostach między blokującym a nieblokującym kodem, np. w testach).
      runBlocking {
          val data = fetchData()
          println(data)
      }
      
  • Kontekst korutyny: Zbiór elementów określających zachowanie korutyny, w tym Dispatcher i Job.
    • Dispatcher: Określa wątek lub pulę wątków, na których wykonywana jest korutyna.
  • Dispatchers: Wbudowane obiekty do zarządzania wątkami:
    • Dispatchers.Default: Wspólna pula wątków w tle.
    • Dispatchers.IO: Optymalizowane dla wejścia/wyjścia (operacje dyskowe, sieciowe).
    • Dispatchers.Main: Główny wątek UI (dla Androida, JavaFX itp.).
    • Dispatchers.Unconfined: Uruchamia korutynę w bieżącym wątku, ale po zawieszeniu może wznowić ją w dowolnym wątku.
    GlobalScope.launch(Dispatchers.IO) {
        // Wykonywane w wątku wejścia/wyjścia
    }
    
  • Strukturalna współbieżność: Zasada, zgodnie z którą korutyny mają rodzica (CoroutineScope) i są automatycznie anulowane przy anulowaniu rodzica. Zapobiega wyciekom zasobów i upraszcza zarządzanie cyklem życia.
    // CoroutineScope automatycznie zarządza korutynami potomnymi
    val scope = CoroutineScope(Dispatchers.Default)
    val job = scope.launch {
        // ...
    }
    // Przy anulowaniu scope, wszystkie korutyny potomne są anulowane
    scope.cancel()
    
  • Kanały: Do bezpiecznego przesyłania danych między korutynami. Mogą być:
    • Bez bufora: Odbiór następuje tylko po wysłaniu.
    • Buforowane: Pozwala na wysłanie kilku elementów przed zablokowaniem.
    • Konkatedowane: Przechowuje tylko ostatni wysłany element.
    val channel = Channel<Int>()
    
    scope.launch {
        for (x in 1..5) channel.send(x)
        channel.close()
    }
    
    scope.launch {
        for (y in channel) println(y)
    }
    
  • Flow: Strumień danych, który może emitować wiele wartości asynchronicznie. Podobny do RxJava Observable, ale lżejszy i zintegrowany z korutynami.
    fun simpleFlow(): Flow<Int> = flow {
        for (i in 1..3) {
            delay(100)
            emit(i) // Wysyłanie wartości
        }
    }
    
    runBlocking {
        simpleFlow().collect { value -> println(value) } // Konsumpcja wartości
    }
    

Funkcje te pozwalają pisać kod asynchroniczny w stylu imperatywnym, czyniąc go bardziej czytelnym i łatwiejszym w utrzymaniu w porównaniu do użycia callbacków lub Future.