Pravila kuhinje
U prvom delu smo videli kako nam launch
i async
omogućavaju da obavljamo više zadataka istovremeno (poput kuvara koji delegira porudžbine).
Kuhinja je prometna i hrana izlazi brže. Međutim, profesionalnoj kuhinji je potrebno više od samih kuvara koji rade paralelno.
Potrebni su joj struktura, upravljanje i protokoli za situacije kada nešto krene po zlu.
Razmotrite sledeće scenarije:
- Gost otkaže porudžbinu na pola pripreme. Da li nastavljamo da mu spremamo jelo?
- Kvari se ključni deo opreme (poput rerne). Da li se cela kuhinja zatvara?
- Određeni zadaci moraju da se obavljaju na određenim stanicama (seckanje naspram serviranja).
Upravo ove probleme rešavaju CoroutineScope, Dispatchers i princip Strukturirane konkurentnosti (Structured Concurrency). Oni predstavljaju sistem upravljanja koji haotičan skup zadataka pretvara u profesionalnu i otpornu operaciju.
Kuhinjske stanice: Dispatchers
Do sada su se naše korutine izvršavale na nekom pozadinskom tredu (thread), ali nismo kontrolisali na kom. Dispatchers su poput posebnih stanica u kuhinji. Oni govore korutini na kom tredu treba da se izvršava.
Kotlin nam nudi tri primarna dispatchera:
Dispatchers.Main
: Deo za posluživanje (gde se hrana servira i služi gostima). Ovo je UI tred (na Androidu). Koristite ga za bilo koji zadatak koji komunicira sa korisničkim interfejsom. Optimizovan je za veoma kratke i brze operacije. Nikada ne blokirajte ovaj tred!Dispatchers.IO
: Skladište. Namenjen je za I/O-intenzivne zadatke, kao što su mrežni pozivi, čitanje iz baze podataka ili pristup fajlovima. Održava veliki broj tredova dizajniranih za zadatke koji većinu vremena provode u čekanju.Dispatchers.Default
: Glavna pripremna stanica. Namenjen je za CPU-intenzivne zadatke, poput sortiranja ogromne liste, složenih izračunavanja ili parsiranja velikih JSON objekata. Veličina njegovog skupa tredova (thread pool) odgovara broju jezgara procesora.
Da bismo prebacili zadatak sa jedne stanice na drugu, koristimo funkciju withContext
.
|
|
Izlaz:
|
|
withContext
je suspend
funkcija koja nam omogućava da pređemo na drugi kontekst za određeni blok koda. Kada se taj kod završi, vraća se nazad.
Ovo je fundamentalni obrazac za asinhrono programiranje sa korutinama.

Glavni kuvar: CoroutineScope
i strukturirana konkurentnost
Ako su Dispatchers
stanice, ko je zadužen za sve kuvare? To je posao CoroutineScope
-a.
Scope je poput glavnog kuvara za grupu korutina. On upravlja njihovim celokupnim životnim ciklusom.
Ovo nas dovodi do principa Strukturirane konkurentnosti (Structured Concurrency). Nove korutine se mogu pokrenuti isključivo unutar scope-a. Scope definiše njihov životni vek. Kada se životni vek scope-a završi, sve korutine unutar njega se automatski otkazuju. Ovo sprečava curenje korutina (npr. da nastave sa preuzimanjem podataka za ekran koji više nije vidljiv).
Scope-ovi koje obezbeđuje framework
Na Androidu dobijate gotove scope-ove vezane za životne cikluse komponenti, koje bi trebalo skoro uvek da koristite:
viewModelScope
: Vezan zaViewModel
. Otkazuje sve korutine kada seViewModel
uništi. Ovo je default izbor na Androidu.lifecycleScope
: Vezan zaLifecycle
aktivnosti ili fragmenta. Koristan je za zadatke koji treba da se usklade sa određenim događajima u životnom ciklusu.
Upozorenje: Izbegavajte
GlobalScope
GlobalScope
je kao odmetnuti kuvar koji radi samostalno i nikada ne ide kući kada se kuhinja zatvori. Korutine pokrenute u njemu nisu vezane ni za jedan posao i lako mogu dovesti do curenja memorije i nepotrebnog trošenja resursa. Skoro da ne postoji dobar razlog za njegovu upotrebu u aplikacionom kodu.
Job
vs SupervisorJob
: Kako se kuvar nosi sa neuspehom
CoroutineScope
je definisan svojim CoroutineContext
-om, koji mora da sadrži Job
. Job
predstavlja životni ciklus samog scope-a i način na koji se on nosi sa neuspesima.
Podrazumevani Job
ima strogu politiku “svi za jednog, jedan za sve”. Ako bilo koja podređena korutina ne uspe i baci izuzetak, ona momentalno otkazuje svoj nadređeni Job
i sve njegove srodne korutine.
To je kao glavni kuvar koji evakuiše celu sekciju ako jedan kuvar izazove požar.
|
|
Izlaz:
|
|
Primetite da Kuvar A nikada nije završio posao. Neuspeh Kuvara B otkazao je ceo scope.
Ali šta ako želite da jedan neuspeh ne utiče na druge zadatke? Za to koristite SupervisorJob
.
On omogućava podređenim korutinama da ne uspeju nezavisno, bez obaranja celog scope-a.
Ovo je blaži glavni kuvar koji se bavi greškom jednog kuvara, dok ostalima govori da nastave sa radom.
|
|
Izlaz:
|
|
viewModelScope
podrazumevano koristi SupervisorJob
, zbog čega jedan neuspeli mrežni poziv u ViewModel
-u ne mora nužno zaustaviti drugi.
To ga čini izuzetno otpornim za zadatke vezane za korisnički interfejs.
Reagovanje u hitnim slučajevima: Obrada izuzetaka
Šta se dešava kada zadatak ne uspe zbog greške? U svetu korutina, izuzeci se propagiraju naviše kroz hijerarhiju.
Neuhvaćeni izuzetak će otkazati svoj nadređeni scope (osim ako se ne radi o SupervisorJob
-u). Ovo je sigurnosna mera poznata kao “fail-fast” (brzo otkazivanje).
Da biste elegantno obradili greške bez rušenja scope-a, koristite try-catch
blok (obično oko .await()
poziva).
|
|
Izlaz:
|
|
Hvatanjem izuzetka, rešili smo problem lokalno i omogućili da se operacija elegantno završi.
Zaključak
Naša kuhinja je sada organizovana. Imamo:
Dispatchers
: Posebne stanice koje osiguravaju da se posao obavlja na pravom mestu (Main, IO, Default).CoroutineScope
: Glavni kuvar koji upravlja životnim ciklusom svih zadataka. On osigurava da ništa ne procuri.Job
vsSupervisorJob
: Različiti stilovi upravljanja za rukovanje neuspesima.- Otkazivanje i izuzeci: Jasni protokoli za situacije kada je porudžbina otkazana ili nešto krene po zlu.
Šta nas čeka u trećem delu?
Do sada su naši kuvari pripremali pojedinačne porudžbine. Gost traži jednu stvar, a mi sa await
čekamo jedan rezultat.
Šta se dešava kada nam je potreban neprekidan tok jela za degustacioni meni, ili švedski sto koji zahteva stalno dopunjavanje?
U trećem delu ćemo predstaviti ključni koncept za obradu tokova podataka: Flow
.
Naučićemo kako da emitujemo, transformišemo i prikupljamo nizove vrednosti tokom vremena, podižući sposobnosti naše kuhinje na sledeći nivo.