Korutyny w Kotlinie to lekkie wątki wspierające wstrzymywanie i wznawianie obliczeń bez blokowania wątków OS. Są uruchamiane przez funkcje takie jak launch, async w obecności nadrzędnego CoroutineScope.
Dyspozycja odbywa się za pomocą obiektów CoroutineDispatcher, które określają, w którym wątku/wykonawcy realizowana jest korutyna (Dispatchers.Main, Dispatchers.IO, Dispatchers.Default).
Przykład:
fun main() = runBlocking { launch(Dispatchers.IO) { val data = getDataFromNetwork() withContext(Dispatchers.Main) { updateUI(data) } } }
Anulowanie: Korutyny są anulowane przez anulowanie ich job/Scope, co powoduje wyrzucenie wewnętrznego wyjątku CancellationException. Korutyny powinny okresowo sprawdzać flagę anulowania lub wywoływać funkcje wstrzymujące, aby poprawnie zakończyć działanie.
Obsługa błędów: Wyjątki w korutynach mogą "znikać" — na przykład, jeśli nie obsłużysz błędu w podrzędnej korutynie, zakończy ona tylko swoją pracę, a nadrzędny nie dowie się o problemie. Istnieją mechanizmy SupervisorJob i CoroutineExceptionHandler w takim przypadku.
Niuansy przekazywania kontekstu:
Co się stanie, jeśli w obrębie nadrzędnej korutyny jedna z podrzędnych zakończy się błędem, a inne będą kontynuować pracę? Czy wszystkie podrzędne zostaną anulowane?
Wielu błędnie uważa, że błąd "pozostaje w podrzędnej".
Prawidłowa odpowiedź: Jeśli używany jest zwykły Job (lub launch), to wszystkie podrzędne korutyny są automatycznie anulowane przy błędzie jakiejkolwiek podrzędnej. Aby podrzędne korutyny nie były anulowane, używa się SupervisorJob lub supervisorScope:
supervisorScope { launch { error("fail") } launch { println("Ten kod będzie działał") } }
Historia
Nieprawidłowa obsługa błędów — awaria flow: W projekcie używano korutyn do ładowania danych. Wyjątki "tonęły" wewnątrz podrzędnych korutyn, przez co poziom retry/recovery nie działał, a główny ekran pozostawał pusty bez żadnych komunikatów o błędach dla użytkownika.
Historia
Zawieszenie UI z powodu pracy na Dispatchers.Main: Młody programista Androida uruchomił ciężkie obliczenia na Dispatchers.Main — UI zaczął "zamrażać". Nie było zrozumienia, że wszelkie ciężkie obliczenia powinny być realizowane na Dispatchers.Default lub Dispatchers.IO.
Historia
AbortError przy anulowaniu nadrzędnego scope: Jeden z programistów nie wziął pod uwagę, że przy anulowaniu nadrzędnego scope wszystkie podrzędne korutyny kończą się anulowaniem, a krytyczna operacja (zapisywanie danych) nie została zakończona.