To co było najważniejsze do przedstawienia w sprawie Kotlina, zostało już wyjaśnione. Przejdziemy do spraw wyższego "tiera", dla ludzi którzy chcą pogłębiać bardziej zaawansowane tematy. Po przebytej chorobie o której wspominałem na Facebooku, demonstruję w dniu dzisiejszym następny temat związany z Kotlinem jakim będzie asynchroniczne wykonywanie kodu. Dowiadujcie się do czego służy słowo "suspend" w języku Kotlin i zamykamy zagadnienie.

"SUSPEND" W JĘZYKU KOTLIN DLA ASYNCHRONICZNYCH FUNKCJI!

O samej współbieżności nie będę się rozpisywać, gdyż pośrednio zrobiłem to już wcześniej pisząc na temat Javy (o asynchroniczności jeszcze artykułu nie ma). Dwa słowa o samym terminie i zasuwamy z tłumaczeniem jak to wygląda w Kotlinie.

WSPÓŁBIEŻNOŚĆ VS. ASYNCHRONICZNOŚĆ

Kiedy mowa o przetwarzaniu współbieżnym lub asynchronicznym, to chodzi o zjawisko programowania zadań wykonywanych w tle. Tradycyjnie, kod źródłowy jest wykonywany raz po raz, instrukcja po instrukcji. Zastosowanie wątków otwiera drogę do egzekwowania kilku czy też nawet kilkunastu zadań jednocześnie, wykonując kod źródłowy z kilku "miejsc". Jest naprawdę użyteczne wszędzie tam, gdzie dane zadanie powinno się wykonywać w tle. Na przykład pobieranie pliku z serwera, które może zająć się sobą w czasie, kiedy my chcemy przeglądać kolejne pliki. Albo "chatroom" na którym pisze się własną wiadomość i w tym samym czasie otrzymuje się je od innych, którzy wysłali.

Często podnoszonym argumentem podczas pogawędek o zaletach współbieżności i asynchroniczności jest zwiększenie skalowalności oprogramowania, czyli stopień efektywności działania aplikacji wraz ze stale rosnącą liczbą użytkowników w tym samym czasie. Jak wszystko w informatyce, ma to również swoje wady. Ale na ich temat skieruję już Was do osobnego artykułu, żeby nie przedłużać.

Trzeba wiedzieć, że asynchroniczność nie równa się współbieżności! Współbieżność dotyczy utworzenia wielu przydziałów zadania dla każdego wątku, a asynchroniczność, wykonywania kilku zadań naraz W JEDNYM wątku! Dlatego poruszymy temat dotyczący asynchroniczności!

KOTLIN POSIADA JAKIEŚ "SUSPEND"...

Na razie postawmy pytanie. Co ma do tego wszystkiego podane słowo "suspend" w języku Kotlin? To jest modyfikator funkcji informujący kompilator, że funkcja będzie mogła podlegać wstrzymaniu wykonywania jej instrukcji w charakterze tak zwanej "koprocedury" (ang. "coroutine"), części kodu charakterystycznej dla przetwarzania asynchronicznego, mającej zdolność do wstrzymywania wykonywania swoich instrukcji w dowolnym momencie.

CO NAM DAJE KOPROCEDURA?

Obsługa każdego następnego wątku obciąża nieco procesor. Kiedy jest ich więcej niż jedna sztuka, siłą rzeczy trzeba się "zająć" nimi wszystkimi. Język Kotlin, oprócz wątków, oferuje ze swojej strony również koprocedury. Różnią się od nich jedną bardzo ważną cechą. W przeciwieństwie do Javy, w której zwykle sięgniemy po obiekt "Thread" i tym samym, utworzymy nowy osobny wątek dla nowego zadania (choć Java także pozwala na tworzenie asynchroniczności), tutaj możemy utworzyć i uruchomić WIELE koprocedur bez zwiększania faktycznej liczby wątków! Czyli jeszcze krócej, wiele koprocedur może zostać utworzonych i przetwarzanych w jednym wątku.

TO JEST ZNACZNIE GRUBSZA SPRAWA...

Ten jeden raz nie mogę od razu przejść do ukazania Wam przykładu kodu źródłowego. Musimy się cofnąć wstecz do procesu tworzenia nowego projektu. Dlaczego?

OD SAMEGO POCZĄTKU

Samo "suspend" w języku Kotlin nie wystarczy, potrzebne nam do tego eksperymentu są jeszcze wspomniane koprocedury. Funkcje pozwalające na tworzenie koprocedur nie występują w standardowej bibliotece Kotlina. Aby móc z nich korzystać, trzeba wgrać je osobno w postaci "zależności". A do tego potrzebny nam będzie "Gradle", narzędzie automatyzujące kompilowanie i wdrażanie kodu źródłowego całego projektu oraz dołączanie wskazanych bibliotek. Pokażę Wam jak w "IntelliJ IDEA" utworzyć projekt w języku Kotlin razem z narzędziem "Gradle" i jak dodać bibliotekę przeznaczoną do korzystania z koprocedur, aż wreszcie przejdziemy do prostego przykładu jak co artykuł.

TWORZENIE NOWEGO PROJEKTU Z WBUDOWANYM "GRADLE"

Po wybraniu opcji "New Project", po lewej stronie znajdować się będą podziały na języki i technologie. Przełączamy na "Gradle". Następnie w prawym oknie ("Additional Libraries and Frameworks") wybieramy sobie co chcemy dołączyć. Aby pójść po linii najmniejszego oporu, wybieramy sobie jedynie "Kotlin/JVM", czyli pisanie w języku Kotlin z możliwością kompilowania do kodu bajtowego wirtualnej maszyny Javy. Klikamy "Next".

Projekt typu "Gradle" w "IntelliJIDEA"

Wybór rodzaju projektu "Gradle" w "IntelliJIDEA".

Po przejściu do następnego okienka, jedynym wymogiem jest nadanie jakiejś sensownej nazwy, a w sekcji "Artifact Coordinates", identyfikator musi być IDENTYCZNY z nazwą. Resztę zostawiam Wam. Wciskamy "Finish".

Tworzenie projektu typu "Gradle" w "IntelliJIDEA"

Nadawanie nazwy projektu i identyfikatora dla projektu "Gradle".

DODANIE ZALEŻNOŚCI DO "GRADLE"

Teraz drugi etap. Dodanie zależności do jednego z plików "Gradle". W oknie widoku zawartości projektu (domyślnie po lewej stronie), wybieramy z całego drzewa taki plik o nazwie "build.gradle" klikając dwukrotnie na jego nazwę. Szukamy w treści frazy "dependencies". Wewnątrz jej klamerek, dodajemy następujący ciąg:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"

Po zapisaniu zmian, trzeba jeszcze zsynchronizować "Gradle", aby zmiany wpłynęły na cały projekt ("IntelliJIDEA" powinien Was o tym powiadomić). Możecie to zrobić przy pomocy kombinacji "Ctrl + Shift + O" albo rozwijając listwę po prawej stronie z napisem "Gradle" ustawionym pionowo i zaznaczając ikonkę kręcących się w kółko strzałek ("Reload All Gradle Projects").

Synchronizacja "Gradle" w "IntelliJIDEA"

Synchronizacja narzędzia "Gradle".

Mamy wszystko gotowe. Pokazuję teraz przykład użycia i o co chodzi z tym "suspend" w języku Kotlin.

PRZYKŁAD WYKORZYSTANIA KOPROCEDUR

Mamy taki przypadek. Obserwujemy sobie postęp procentowy wczytywania jakiegoś pliku z danymi. Proces ładowania ma iść swoim torem, ale chcemy co jakiś czas znać obecny procent załadowania danych w postaci wypisania komunikatu na strumieniu wyjściowym. Pętla też by się sprawdziła, ale wtedy co każdą iterację następowałoby wyświetlenie komunikatu albo konieczne byłoby użycie instrukcji warunkowej razem z zaprogramowaniem licznika wykonanych klatek od uruchomienia programu. My chcemy to wypisywać załóżmy, co cztery sekundy. Umożliwią nam to koprocedury i słowo "suspend" w języku Kotlin!

Pierwszą rzeczą będzie zaimportowanie niezbędnych bibliotek. Potrzebne będą cztery następujące sztuki:

import kotlin.random.Random
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

Do tego dorzucamy sobie dwie zmienne globalne, jedna typu "var", a druga może być "val", gdyż to będzie stała:

var loadedBytes = 0

const val bytesCount = 80

Teraz najważniejszy punkt programu, czyli zaprogramowanie samej symulacji wczytywania danych. Definiujemy sobie własne funkcje, których kody źródłowe prezentują się w sposób następujący:

suspend fun loadFile()
{
	while (fileIsBeingLoaded())
	{
		if(fileIsBeingLoaded())
		{
			loadedBytes += Random.nextInt(2, 6)

			if(loadedBytes > bytesCount)
			{
				loadedBytes = bytesCount
			}

			delay(1000)
		}
	}
}

suspend fun showLoadingProgress()
{
	while (fileIsBeingLoaded())
	{
		delay(4000)

		val percent = ((loadedBytes.toDouble() / bytesCount.toDouble())*100).toInt()

		println("Postęp wczytywania pliku: $loadedBytes/$bytesCount bajtów ($percent%)")
	}
}

fun fileIsBeingLoaded() = loadedBytes < bytesCount

za to w funkcji "main" wprowadzamy "zapalnik" naszej funkcji, czyli:

suspend fun main(args : Array<String>)
{
	runBlocking {
		launch { loadFile() }
		showLoadingProgress()
	}
	
	println("Zakończone!")
}

Jestem przekonany, że niektóre miejsca można zapisać lepiej, tylko nie chcę przyćmiewać Wam niniejszego tematu. Po uruchomieniu powyższego kodu (po jego uprzednim połączeniu w jeden plik źródłowy), ujrzymy na wyjściu monitorowanie stanu pobierania pliku co cztery sekundy, aż dojdzie do finalizacji. Oprócz ujrzenia efektu końcowego, trzeba sobie wyjaśnić parę nowości.

TWORZENIE BLOKU DLA KOPROCEDUR

"runBlocking" to jedna z funkcji wyższego rzędu tworząca zasięg dla instrukcji mających się wykonywać asynchronicznie w postaci koprocedury. W chwili uruchomienia koprocedury, blokuje bieżący wątek dopóki nie zakończy swojego zadania. Jak już dobrze wiecie, funkcja wyższego rzędu to funkcja przyjmująca za parametr lambdę albo zwracająca lambdę. Nie widzę zatem przeszkód, żeby wewnątrz niej nie wstawić sobie tylu funkcji ile chcę.

URUCHAMIANIE KOPROCEDURY

"launch" uruchamia nową koprocedurę bez blokowania obecnego wątku. Notabene, że wystarczy opatrzenie tylko jednej funkcji "launch"! Nie wolno objąć obu funkcji, gdyż wyświetlanie postępu nigdy się nie wykona!

WSTRZYMYWANIE KOPROCEDURY

Za wstrzymywanie koprocedur odpowiada funkcja "delay". "delay" możecie rozumieć jak "Thread.sleep" w Javie. Z tym że to drugie "usypia" wątki, a "delay", koprocedury. W związku z tym, trzeba wywoływać "delay" podczas korzystania z koprocedur, bo "Thread.sleep" nie przyniesie oczekiwanego rezultatu. Pamiętajcie, że wykonujemy dwa zadania posługując się tym samym wątkiem, nie dwoma osobnymi!

NIE ZAPOMNIJ O TYM!

Jeszcze jedna istotna rzecz w sprawie "suspend" w języku Kotlin! Modyfikator ten musi się znaleźć nie tylko obok funkcji "robiącej" za koprocedurę, ale także obok funkcji, która ten manewr inicjuje! Zatem, w naszej sytuacji "suspend" musi się znaleźć obok funkcji "loadFile", funkcji "showLoadingProgress", ale także obok "main", gdyż w niej znajduje się inicjator jakim jest "runBlocking"!

Funkcja "runBlocking" w języku Kotlin

Tworzenie kodu działającego asynchronicznie wymaga narzędzia "Gradle" oraz pobrania zależności w postaci biblioteki "coroutines".

Słowo kluczowe "suspend" w języku Kotlin

"suspend" informuje kompilator, że dana funkcja może podlegać wstrzymaniom pod wpływem funkcji "delay" wywołanej wewnątrz niej.


Wniosek końcowy. Jeśli piszecie sobie programowanie zadań w tle w Kotlinie, nie interesujcie się wątkami. Zdecydowanie lepiej będzie uśmiechnąć się do koprocedur. Duży plus ich wykorzystywania odbija się na liczbie istniejących wątków (wiele koprocedur w jednym wątku), a "suspend" w języku Kotlin umożliwi Wam wywołanie funkcji wstrzymującej tę wykonywaną asynchronicznie.

PODOBNE ARTYKUŁY