Aplikacija se zamrzla? Kako to rešiti?
Ako ste ikada pravili aplikaciju sa korisničkim interfejsom, sigurno ste se našli u ovoj situaciji: pokrenete poziv preko mreže i odjednom, cela aplikacija se zamrzne. Dugmići ne reaguju, animacije se zaustavljaju, a korisnici postaju frustrirani. Ovo je klasičan problem dugotrajnih zadataka na glavnoj (main) niti. Upravo to rešavaju Kotlin korutine.
Daju nam moćan način da pišemo asinhroni kod koji izgleda i ponaša se kao jednostavan, sekvencijalan, sinhroni kod. Nema više callback hell-a!
Da biste ovo zaista razumeli, zamislite kuvara u kuhinji. Kuvar može da radi samo jednu po jednu stvar, baš kao i main UI nit aplikacije.
Evo recepta:
- Seče povrće (CPU zadatak).
- Stavlja ga u mikrotalasnu na 2 minuta (I/O zadatak).
- Priprema salatu dok čeka (CPU zadatak).
- Servira jelo.
Blokirajući pristup (Neefikasna kuhinja)
Bez korutina, kuvar radi ovako:
- Seče povrće.
- Stavlja ga u mikrotalasnu, pritiska start, i onda… stoji i gleda u tajmer pune dva minuta. Ništa drugo se ne dešava.
- Kada mikrotalasna konačno zapišti, kuvar se vraća i počinje da pravi salatu.
Rezultat? Izuzetno neefikasna kuhinja. Kuvar je u potpunosti blokiran. U stvarnoj aplikaciji, to znači zamrznut UI i frustriranog korisnika (i recenziju sa jednom zvezdicom koja samo što nije napisana 🙂).
|
|
Izlaz:
|
|
Ta pauza od 2 sekunde je pogubna za korisnika. To moramo popraviti.
Suspendujući pristup (Korutinska kuhinja)
Kako korutine ovo postižu? Tajna je u ključnoj reči suspend
.
suspend
funkcija je posebna. Ona govori Kotlin kompajleru: „Hej, ova funkcija može potrajati. Slobodno je pauziraj ovde i pusti nit da radi nešto drugo.
Javiću ti kada budem spreman da nastavim.“
Novi, efikasni kuvar radi ovako:
- Seče povrće.
- Stavlja ga u mikrotalasnu, pritiska start i odmah odlazi da radi druge stvari. Ovo je tačka suspenzije. Kuvar je slobodan!
- Priprema salatu.
- Kada mikrotalasna zapišti, kuvar dobija obaveštenje i vraća se po hranu.
Ali, ne možete tek tako pozvati suspend
funkciju kad god želite.
Morate je pokrenuti unutar korutine. Tu na scenu stupaju Coroutine Builders. Oni su naša ulazna tačka u ovaj novi, neblokirajući svet.
Fire and Forget sa launch
Počnimo sa najjednostavnijim slučajem: treba da pokrenemo mikrotalasnu u pozadini. Ne treba nam odmah povratni rezultat. Samo želimo da ispalimo zadatak.
Za ovo koristimo launch
bilder. Zamislite kao da kažete kuvaru: „Idi uradi ovo. Ne treba mi ništa od tebe odmah, samo završi posao.“
Brzo, ali važno upozorenje: Da bismo ovo pokrenuli u
main
funkciji, koristimo poseban bilderrunBlocking
. On je dizajniran da premosti blokirajući svet sa suspendujućim svetom korutina. On će blokirati glavnu nit dok se svaka korutina unutar njega ne završi. Ovo je odlično za demonstracije i testove, ali NIKADA ga ne koristite u produkcionom Android kodu.
|
|
Izlaz:
|
|
Kuvar odmah počinje sa salatom. Zadatak sa mikrotalasnom se izvršava konkurentno u pozadini. Postigli smo pravu neblokirajuću konkurentnost.
Dobijanje rezultata pomoću async
i await
U redu, launch
je sjajan za pokretanje pozadinskih poslova. Ali budimo realni, najčešće ne radimo po principu „fire and forget“, već obično preuzimamo podatke.
Potreban nam je rezultat.
Zamislimo da je kuvaru potreban poseban sos. On kaže svom pomoćniku da ga napravi. Kuvar može da nastavi sa radom, ali u nekom trenutku će morati da stane i sačeka taj sos pre nego što završi jelo.
Ovo je posao za async
. To je još jedan bilder, ali umesto Job
-a, on nam vraća nešto što se zove Deferred<T>
.
To je obećanje da će sadržati našu vrednost u nekom trenutku.
Da bismo dobili vrednost, pozivamo .await()
. I tu je ključna stvar: .await()
je suspend
funkcija.
Ako sos nije gotov, kuvar će se tu pauzirati (bez blokiranja niti!) dok ne bude.
|
|
Izlaz:
|
|
Ovo je lepota svega. Kod se i dalje čita od vrha do dna, kao priča.
Nema callback-ova, nema komplikovanih reaktivnih lanaca. Samo pokrenemo zadatak sa async
i sačekamo rezultat sa await
kada nam zatreba.
Zaključak
To je to što se tiče osnova! Pokrili smo srž korutina:
- Zašto: Blokiranje niti zamrzava aplikacije;
suspend
funkcije su rešenje. - „Fire and Forget“: Koristite
launch
kada samo želite da pokrenete pozadinski zadatak. - Dobijanje rezultata: Koristite
async
da pokrenete zadatak koji vraća vrednost, a.await()
da dobijete tu vrednost kada budete spremni.
Šta nas čeka u drugom delu?
Naša kuhinja radi, ali deluje pomalo… magično. Gde se pozadinski zadaci zapravo izvršavaju? Šta se dešava ako mušterija otkaže porudžbinu na pola posla? Da li kuvari nastavljaju da kuvaju zauvek, trošeći resurse?
Zaronićemo dublje u mehanizme koji korutine čine tako robusnim.
Pričaćemo o CoroutineScope
-u, životnom ciklusu Job
-ova i Dispatcher
-ima da bismo videli kako možemo da upravljamo našim korutinama i kažemo im u kom tačno delu kuhinje da rade.
Čitamo se u sledećem delu