Czytam Kotlin Coroutine i wiem, że opiera się na suspend
funkcji. Ale co to suspend
znaczy?
Program lub funkcja zostaje zawieszona?
Z https://kotlinlang.org/docs/reference/coroutines.html
Zasadniczo programy te to obliczenia, które można zawiesić bez blokowania wątku
Słyszałem, jak ludzie często mówią „wstrzymaj funkcję”. Ale myślę, że to coroutine zostaje zawieszony, ponieważ czeka na zakończenie funkcji? „zawieszenie” zwykle oznacza „zaprzestanie działania”, w tym przypadku program jest bezczynny.
🤔 Czy powinniśmy powiedzieć, że program jest zawieszony?
Który program zostaje zawieszony?
Z https://kotlinlang.org/docs/reference/coroutines.html
Kontynuując analogię, await () może być funkcją wstrzymującą (stąd również wywoływaną z poziomu bloku async {}), która zawiesza program do czasu wykonania pewnych obliczeń i zwraca jego wynik:
async { // Here I call it the outer async coroutine
...
// Here I call computation the inner coroutine
val result = computation.await()
...
}
🤔 Jest tam napisane „że zawiesza program do czasu wykonania niektórych obliczeń”, ale program ten jest jak lekki wątek. Jeśli więc program jest zawieszony, jak można wykonać obliczenia?
Widzimy, że await
jest wywoływana computation
, więc może się zdarzyć, async
że powróci Deferred
, co oznacza, że może rozpocząć kolejny program
fun computation(): Deferred<Boolean> {
return async {
true
}
}
🤔 Cytat mówi, że zawiesza program . Czy to oznacza suspend
zewnętrzny async
rdzeń, czy suspend
wewnętrzny computation
rdzeń?
Czy suspend
oznacza, że gdy zewnętrzny async
program czeka ( await
) na zakończenie wewnętrznego computation
programu, to (zewnętrzny async
program) pozostaje bezczynny (stąd nazwa zawieszona) i zwraca wątek do puli wątków, a kiedy program potomny computation
kończy, to (zewnętrzny async
program ) budzi się, bierze kolejny wątek z puli i kontynuuje?
Powodem, dla którego wspominam o wątku, jest https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html
Wątek jest zwracany do puli, gdy program oczekuje, a po zakończeniu oczekiwania proces zostaje wznowiony na wolnym wątku w puli
źródło
suspend fun
można to zatrzymać, ale jak dokładnie?Aby zrozumieć, co dokładnie oznacza zawieszenie programu, proponuję przejść przez ten kod:
Unconfined
Współprogram dyspozytor eliminuje magię współprogram dyspozytorni i pozwala nam skupić się bezpośrednio na gołe współprogram.Kod wewnątrz
launch
bloku zaczyna być wykonywany od razu w bieżącym wątku, jako częśćlaunch
wywołania. Oto co się dzieje:val a = a()
b()
, sięganiasuspendCoroutine
.b()
wykonuje przekazany blok,suspendCoroutine
a następnie zwraca specjalnąCOROUTINE_SUSPENDED
wartość. Ta wartość nie jest obserwowalna w modelu programowania Kotlin, ale to właśnie robi skompilowana metoda Java.a()
, widząc tę zwracaną wartość, również ją zwraca.launch
Blok robi to samo i sterowanie powraca do linii polaunch
wywołaniu:10.downTo(0)...
Zauważ, że w tym momencie masz taki sam efekt, jak gdyby kod wewnątrz
launch
bloku i twójfun main
kod były wykonywane jednocześnie. Tak się po prostu dzieje, że wszystko to dzieje się na pojedynczym natywnym wątku, więclaunch
blok jest „zawieszany”.Teraz, w
forEach
kodzie zapętlonym, program odczytuje tocontinuation
, cob()
napisała funkcjaresumes
, z wartością10
.resume()
jest zaimplementowany w taki sposób, że będzie tak, jakbysuspendCoroutine
wywołanie zwróciło wartość, którą przekazałeś. Więc nagle znajdujesz się w środku wykonywaniab()
. Wartość, do której przekazałeś,resume()
zostanie przypisanai
i sprawdzona0
. Jeśli nie jest równe zero,while (true)
pętla jest kontynuowana w środkub()
, ponownie osiągającsuspendCoroutine
punkt, w którymresume()
połączenie powraca, a teraz przechodzisz przez kolejny krok pętliforEach()
. Trwa to do momentu, gdy w końcu wznowisz działanie0
, po czymprintln
instrukcja zostanie uruchomiona i program zakończy działanie.Powyższa analiza powinna dać ci ważną intuicję, że "zawieszenie programu" oznacza przywrócenie kontroli z powrotem do najgłębszej
launch
inwokacji (lub, bardziej ogólnie, konstruktora programu ). Jeśli program ponownie zawiesza się po wznowieniu,resume()
połączenie kończy się i sterowanie wraca do dzwoniącegoresume()
.Obecność programu wysyłającego sprawia, że to rozumowanie jest mniej jasne, ponieważ większość z nich natychmiast przesyła twój kod do innego wątku. W takim przypadku powyższa historia dzieje się w tym innym wątku, a program wysyłający również zarządza
continuation
obiektem, aby mógł go wznowić, gdy wartość zwracana będzie dostępna.źródło
Po pierwsze, najlepszym źródłem zrozumienia tej IMO jest wykład Romana Elizarova „Deep Dive into Coroutines” .
Wywołanie zawiesić ing funkcję zawiesić przyjmuje S z współprogram, czyli aktualny wątek może rozpocząć wykonywanie innej współprogram. Mówi się więc, że program jest raczej zawieszony niż funkcja.
W rzeczywistości strony wywołań funkcji zawieszających nazywane są z tego powodu „punktami zawieszenia”.
Spójrzmy na Twój kod i wyjaśnijmy, co się dzieje:
Zewnętrzna
async
zaczyna się coroutine. Kiedy wołacomputation()
, wewnętrznaasync
rozpoczyna drugą procedurę. Następnie wezwanie do wstrzymaniaawait()
wykonywania zewnętrznegoasync
programu, aż do zakończenia wykonywania wewnętrznego programu wewnętrznegoasync
.Możesz to nawet zobaczyć z pojedynczym wątkiem: wątek wykona
async
początek zewnętrznego , a następnie wywołacomputation()
i osiągnie wewnętrznyasync
. W tym momencie treść wewnętrznej asynchronicznej jest pomijana, a wątek kontynuuje wykonywanie zewnętrznej,async
dopóki nie osiągnieawait()
.await()
jest „punktem zawieszenia”, ponieważawait
pełni funkcję zawieszenia. Oznacza to, że zewnętrzny program zostaje zawieszony, a tym samym nić zaczyna wykonywać wewnętrzny. Kiedy to się skończy, wraca, aby wykonać koniec zewnętrznejasync
.Tak, dokładnie.
Sposób, w jaki jest to faktycznie osiągane, polega na przekształceniu każdej funkcji wstrzymującej w maszynę stanu, gdzie każdy „stan” odpowiada punktowi zawieszenia wewnątrz tej funkcji zawieszenia. Pod maską funkcję można wywołać wielokrotnie, z informacją, od którego punktu zawieszenia powinna zacząć się uruchamiać (naprawdę powinieneś obejrzeć wideo, które podlinkowałem, aby uzyskać więcej informacji na ten temat).
źródło
async
funkcje JS są oznaczone w ten sposób, a mimo to zwracają obietnicę.Odkryłem, że najlepszym sposobem na zrozumienie
suspend
jest dokonanie analogii międzythis
słowem kluczowym acoroutineContext
właściwością.Funkcje Kotlina można zadeklarować jako lokalne lub globalne. Funkcje lokalne mają magiczny dostęp do
this
słów kluczowych, podczas gdy globalne nie.Funkcje Kotlina można zadeklarować jako
suspend
lub blokujące.suspend
funkcje mają magiczny dostęp docoroutineContext
właściwości, podczas gdy funkcje blokujące nie.Rzecz w tym, że
coroutineContext
właściwość jest zadeklarowana jako "normalna" właściwość w standardowym katalogu Kotlin, ale ta deklaracja jest tylko skrótem do celów dokumentacji / nawigacji. W rzeczywistościcoroutineContext
jest wbudowaną wewnętrzną własnością, co oznacza, że kompilator pod maską jest świadomy tej właściwości, tak jak ma świadomość słów kluczowych języka.To, co
this
słowo kluczowe robi dla funkcji lokalnych, jest tym, cocoroutineContext
właściwość robi dlasuspend
funkcji: daje dostęp do bieżącego kontekstu wykonania.Musisz
suspend
więc uzyskać dostęp docoroutineContext
właściwości - instancji aktualnie wykonywanego kontekstu programuźródło
Chciałem podać prosty przykład koncepcji kontynuacji. To właśnie robi funkcja wstrzymania, może zawiesić / zawiesić, a następnie kontynuować / wznowić. Przestań myśleć o programie w kategoriach wątków i semafora. Pomyśl o tym w kategoriach kontynuacji, a nawet połączeń zwrotnych.
Aby było jasne, program można wstrzymać za pomocą
suspend
funkcji. zbadajmy to:W Androidzie możemy to zrobić na przykład:
Powyższy kod drukuje:
wyobraź sobie, jak to działa:
Tak więc bieżąca funkcja, z której uruchomiłeś, nie zatrzymuje się, po prostu program zawiesi się, gdy będzie kontynuowany. Wątek nie jest wstrzymywany przez uruchomienie funkcji wstrzymania.
Myślę, że ta strona może ci pomóc w prostym rozwiązaniu i jest moim punktem odniesienia.
Zróbmy coś fajnego i zatrzymajmy naszą funkcję wstrzymania w środku iteracji. Wrócimy do tego później
onResume
Zapisz zmienną o nazwie,
continuation
a my załadujemy ją z obiektem kontynuacji coroutines:Wróćmy teraz do naszej zawieszonej funkcji i zamrozić ją w środku iteracji:
Następnie gdzieś indziej, na przykład w onResume:
I pętla będzie kontynuowana. Dobrze jest wiedzieć, że w dowolnym momencie możemy zamrozić funkcję wstrzymania i wznowić ją po upływie pewnego czasu. Możesz także zajrzeć do kanałów
źródło
Ponieważ istnieje już wiele dobrych odpowiedzi, chciałbym zamieścić prostszy przykład dla innych.
suspend
funkcjąrunBlocking { }
uruchamia Coroutine w sposób blokujący. Jest to podobne do tego, jak blokowaliśmy normalne wątkiThread
klasą i powiadamialiśmy o zablokowanych wątkach po pewnych zdarzeniach.runBlocking { }
nie blokuje aktualnie wykonywanego wątku, aż do zakończenia procedury (body pomiędzy{}
)To daje:
launch { }
uruchamia program jednocześnie.worker
wątku.worker
Gwintu i gwint zewnętrzny (z których nazywalaunch { }
), zarówno biegnie równolegle. Wewnętrznie maszyna JVM może wykonywać zapobiegawcze tworzenie wątkówGdy wymagamy równoległego wykonywania wielu zadań, możemy to wykorzystać. Istnieją,
scopes
które określają czas życia programu. Jeśli określimyGlobalScope
, program będzie działał do końca życia aplikacji.To daje:
async
iawait
pomogłyby.2
funkcje wstrzymania myMethod () i myMethod2 ().myMethod2()
powinien zostać wykonany dopiero po całkowitym wypełnieniumyMethod()
ORmyMethod2()
zależy od wynikumyMethod()
, możemy użyćasync
iawait
async
uruchamia program równolegle podobny dolaunch
. Zapewnia jednak sposób, aby poczekać na jeden program przed równoległym uruchomieniem innego programu.Tak jest
await()
.async
zwraca wystąpienieDeffered<T>
.T
byłbyUnit
domyślny. Kiedy musimy czekać naasync
zakończenie, musimy odwołać.await()
sięDeffered<T>
do tegoasync
. Jak w poniższym przykładzie, wywołaliśmy,innerAsync.await()
co oznacza, że wykonanie zostanie zawieszone do czasuinnerAsync
zakończenia. To samo możemy zaobserwować w produkcji.innerAsync
Zostanie zakończony pierwszy, który wzywamyMethod()
. A potemasync
innerAsync2
zaczyna się następny , który dzwonimyMethod2()
To daje:
źródło