Co oznacza funkcja wstrzymania w Kotlin Coroutine

118

Czytam Kotlin Coroutine i wiem, że opiera się na suspendfunkcji. Ale co to suspendznaczy?

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 awaitjest 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 suspendzewnętrzny asyncrdzeń, czy suspendwewnętrzny computationrdzeń?

Czy suspendoznacza, że ​​gdy zewnętrzny asyncprogram czeka ( await) na zakończenie wewnętrznego computationprogramu, to (zewnętrzny asyncprogram) pozostaje bezczynny (stąd nazwa zawieszona) i zwraca wątek do puli wątków, a kiedy program potomny computationkończy, to (zewnętrzny asyncprogram ) 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

onmyway133
źródło

Odpowiedzi:

113

Funkcje zawieszenia znajdują się w centrum wszystkich programów. Funkcja wstrzymania to po prostu funkcja, którą można wstrzymać i wznowić w późniejszym czasie. Mogą wykonywać długotrwałą operację i czekać na jej zakończenie bez blokowania.

Składnia funkcji zawieszającej jest podobna do składni zwykłej funkcji, z wyjątkiem dodania suspendsłowa kluczowego. Może przyjmować parametr i zwracać typ. Jednak funkcje wstrzymujące mogą być wywoływane tylko przez inną funkcję wstrzymującą lub w ramach programu.

suspend fun backgroundTask(param: Int): Int {
     // long running operation
}

Pod maską funkcje suspend są konwertowane przez kompilator na inną funkcję bez słowa kluczowego suspend, która przyjmuje dodatkowy parametr typu Continuation<T>. Na przykład powyższa funkcja zostanie przekonwertowana przez kompilator na następującą:

fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
   // long running operation
}

Continuation<T> jest interfejsem zawierającym dwie funkcje, które są wywoływane w celu wznowienia programu z wartością zwracaną lub z wyjątkiem, jeśli wystąpił błąd, gdy funkcja została zawieszona.

interface Continuation<in T> {
   val context: CoroutineContext
   fun resume(value: T)
   fun resumeWithException(exception: Throwable)
}
Sofien Rahmouni
źródło
4
Odsłonięta kolejna tajemnica! Wspaniały!
WindRider,
16
Zastanawiam się, w jaki sposób ta funkcja została faktycznie wstrzymana? Zawsze mówią, że suspend funmożna to zatrzymać, ale jak dokładnie?
WindRider,
2
@WindRider Oznacza to po prostu, że bieżący wątek zaczyna wykonywać inny program i wróci do tego później.
Joffrey
2
Odkryłem „tajemniczy” mechanizm. Można go łatwo odsłonić za pomocą Narzędzia> Kotlin> Kod bajtowy> Dekompiluj btn. Pokazuje, jak realizowany jest tak zwany „punkt zawieszenia” - poprzez kontynuację i tak dalej. Każdy może się przekonać.
WindRider,
4
@buzaa Oto przemówienie Romana Elizarova z 2017 roku, które wyjaśnia to na poziomie kodu bajtowego.
Marko Topolnik
30

Aby zrozumieć, co dokładnie oznacza zawieszenie programu, proponuję przejść przez ten kod:

import kotlinx.coroutines.Dispatchers.Unconfined
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main() = runBlocking {
    launch(Unconfined) {
        val a = a()
        println("Result is $a")
    }
    10.downTo(0).forEach {
        continuation!!.resume(it)
    }
}

suspend fun a(): Int {
    return b()
}

suspend fun b(): Int {
    while (true) {
        val i = suspendCoroutine<Int> { cont -> continuation = cont }
        if (i == 0) {
            return 0
        }
    }
}

UnconfinedWspółprogram dyspozytor eliminuje magię współprogram dyspozytorni i pozwala nam skupić się bezpośrednio na gołe współprogram.

Kod wewnątrz launchbloku zaczyna być wykonywany od razu w bieżącym wątku, jako część launchwywołania. Oto co się dzieje:

  1. Oceniać val a = a()
  2. To łańcuchy do b(), sięgania suspendCoroutine.
  3. Funkcja b()wykonuje przekazany blok, suspendCoroutinea następnie zwraca specjalną COROUTINE_SUSPENDEDwartość. Ta wartość nie jest obserwowalna w modelu programowania Kotlin, ale to właśnie robi skompilowana metoda Java.
  4. Funkcja a(), widząc tę ​​zwracaną wartość, również ją zwraca.
  5. launchBlok robi to samo i sterowanie powraca do linii po launchwywołaniu:10.downTo(0)...

Zauważ, że w tym momencie masz taki sam efekt, jak gdyby kod wewnątrz launchbloku i twój fun mainkod były wykonywane jednocześnie. Tak się po prostu dzieje, że wszystko to dzieje się na pojedynczym natywnym wątku, więc launchblok jest „zawieszany”.

Teraz, w forEachkodzie zapętlonym, program odczytuje to continuation, co b()napisała funkcja resumes, z wartością 10. resume()jest zaimplementowany w taki sposób, że będzie tak, jakby suspendCoroutinewywołanie zwróciło wartość, którą przekazałeś. Więc nagle znajdujesz się w środku wykonywania b(). Wartość, do której przekazałeś, resume()zostanie przypisana ii sprawdzona 0. Jeśli nie jest równe zero, while (true)pętla jest kontynuowana w środku b(), ponownie osiągając suspendCoroutinepunkt, w którym resume()połączenie powraca, a teraz przechodzisz przez kolejny krok pętli forEach(). Trwa to do momentu, gdy w końcu wznowisz działanie 0, po czym printlninstrukcja 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 launchinwokacji (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ącego resume().

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 continuationobiektem, aby mógł go wznowić, gdy wartość zwracana będzie dostępna.

Marko Topolnik
źródło
21

Po pierwsze, najlepszym źródłem zrozumienia tej IMO jest wykład Romana Elizarova „Deep Dive into Coroutines” .

Program lub funkcja zostaje zawieszona?

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”.

Który program zostaje zawieszony?

Spójrzmy na Twój kod i wyjaśnijmy, co się dzieje:

// 1. this call starts a new coroutine (let's call it C1).
//    If there were code after it, it would be executed concurrently with
//    the body of this async
async {
    ...
    // 2. this is a regular function call
    val deferred = computation()
    // 4. because await() is suspendING, it suspends coroutine C1.
    //    This means that if we had a single thread in our dispatcher, 
    //    it would now be free to go execute C2
    // 7. once C2 completes, C1 is resumed with the result `true` of C2's async
    val result = deferred.await() 
    ...
    // 8. C1 can now keep going in the current thread until it gets 
    //    suspended again (or not)
}

fun computation(): Deferred<Boolean> {
    // 3. this async call starts a second coroutine (C2). Depending on the 
    //    dispatcher you're using, you may have one or more threads.
    // 3.a. If you have multiple threads, the block of this async could be
    //      executed in parallel of C1 in another thread. The control flow 
    //      of the current thread returns to the caller of computation().
    // 3.b. If you have only one thread, the block is sort of "queued" but 
    //      not executed right away, and the control flow returns to the 
    //      caller of computation(). (unless a special dispatcher or 
    //      coroutine start argument is used, but let's keep it simple).
    //    In both cases, we say that this block executes "concurrently"
    //    with C1.
    return async {
        // 5. this may now be executed
        true
        // 6. C2 is now completed, so the thread can go back to executing 
        //    another coroutine (e.g. C1 here)
    }
}

Zewnętrzna asynczaczyna się coroutine. Kiedy woła computation(), wewnętrzna asyncrozpoczyna drugą procedurę. Następnie wezwanie do wstrzymania await()wykonywania zewnętrznego async programu, aż do zakończenia wykonywania wewnętrznego programu wewnętrznegoasync .

Możesz to nawet zobaczyć z pojedynczym wątkiem: wątek wykona asyncpoczątek zewnętrznego , a następnie wywoła computation()i osiągnie wewnętrzny async. W tym momencie treść wewnętrznej asynchronicznej jest pomijana, a wątek kontynuuje wykonywanie zewnętrznej, asyncdopóki nie osiągnie await(). await()jest „punktem zawieszenia”, ponieważ awaitpeł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ętrznej async.

Czy zawieszenie oznacza, że ​​podczas gdy zewnętrzny program asynchroniczny czeka (czeka) na zakończenie wewnętrznego programu obliczeniowego, to (zewnętrzny program asynchroniczny) pozostaje bezczynny (stąd nazwa zawieszona) i zwraca wątek do puli wątków, a gdy proces podrzędny obliczeń kończy się , to (zewnętrzny program asynchroniczny) budzi się, pobiera kolejny wątek z puli i kontynuuje?

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).

Joffrey
źródło
3
Świetna odpowiedź, brakuje mi tego rodzaju naprawdę podstawowych wyjaśnień, jeśli chodzi o programy.
bernardo.g
Dlaczego nie jest to zaimplementowane w żadnym innym języku? A może coś mi brakuje? Tak długo myślę o tym rozwiązaniu, cieszę się, że Kotlin je ma, ale nie wiem, dlaczego TS lub Rust mają coś takiego
PEZO
@PEZO well coroutines istnieją już od dawna. Kotlin ich nie wymyślił, ale składnia i biblioteka nadają im blasku. Go ma goroutines, JavaScript i TypeScript mają obietnice. Jedyną różnicą są szczegóły składni ich użycia. Uważam to za dość denerwujące / niepokojące, gdy asyncfunkcje JS są oznaczone w ten sposób, a mimo to zwracają obietnicę.
Joffrey
Przepraszam, mój komentarz nie był jasny. Mam na myśli słowo kluczowe suspend. To nie to samo, co asynchronizacja.
PEZO
Dziękuję za wskazanie filmu Romana. Czyste złoto.
Denounce'IN
8

Odkryłem, że najlepszym sposobem na zrozumienie suspendjest dokonanie analogii między thissłowem kluczowym a coroutineContextwłaściwością.

Funkcje Kotlina można zadeklarować jako lokalne lub globalne. Funkcje lokalne mają magiczny dostęp do thissłów kluczowych, podczas gdy globalne nie.

Funkcje Kotlina można zadeklarować jako suspendlub blokujące. suspendfunkcje mają magiczny dostęp do coroutineContextwłaściwości, podczas gdy funkcje blokujące nie.

Rzecz w tym, że coroutineContextwł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ści coroutineContextjest 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 thissłowo kluczowe robi dla funkcji lokalnych, jest tym, co coroutineContextwłaściwość robi dla suspendfunkcji: daje dostęp do bieżącego kontekstu wykonania.

Musisz suspendwięc uzyskać dostęp do coroutineContextwłaściwości - instancji aktualnie wykonywanego kontekstu programu

Dmitrij Kolesnikowicz
źródło
5

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ą suspendfunkcji. zbadajmy to:

W Androidzie możemy to zrobić na przykład:

var TAG = "myTAG:"
        fun myMethod() { // function A in image
            viewModelScope.launch(Dispatchers.Default) {
                for (i in 10..15) {
                    if (i == 10) { //on first iteration, we will completely FREEZE this coroutine (just for loop here gets 'suspended`)
                        println("$TAG im a tired coroutine - let someone else print the numbers async. i'll suspend until your done")
                        freezePleaseIAmDoingHeavyWork()
                    } else
                        println("$TAG $i")
                    }
            }

            //this area is not suspended, you can continue doing work
        }


        suspend fun freezePleaseIAmDoingHeavyWork() { // function B in image
            withContext(Dispatchers.Default) {
                async {
                    //pretend this is a big network call
                    for (i in 1..10) {
                        println("$TAG $i")
                        delay(1_000)//delay pauses coroutine, NOT the thread. use  Thread.sleep if you want to pause a thread. 
                    }
                    println("$TAG phwww finished printing those numbers async now im tired, thank you for freezing, you may resume")
                }
            }
        }

Powyższy kod drukuje:

I: myTAG: my coroutine is frozen but i can carry on to do other things

I: myTAG: im a tired coroutine - let someone else print the numbers async. i'll suspend until your done

I: myTAG: 1
I: myTAG: 2
I: myTAG: 3
I: myTAG: 4
I: myTAG: 5
I: myTAG: 6
I: myTAG: 7
I: myTAG: 8
I: myTAG: 9
I: myTAG: 10

I: myTAG: phwww finished printing those numbers async now im tired, thank you for freezing, you may resume

I: myTAG: 11
I: myTAG: 12
I: myTAG: 13
I: myTAG: 14
I: myTAG: 15

wyobraź sobie, jak to działa:

wprowadź opis obrazu tutaj

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óźniejonResume

Zapisz zmienną o nazwie, continuationa my załadujemy ją z obiektem kontynuacji coroutines:

var continuation: CancellableContinuation<String>? = null

suspend fun freezeHere() = suspendCancellableCoroutine<String> {
            continuation = it
        }

 fun unFreeze() {
            continuation?.resume("im resuming") {}
        }

Wróćmy teraz do naszej zawieszonej funkcji i zamrozić ją w środku iteracji:

 suspend fun freezePleaseIAmDoingHeavyWork() {
        withContext(Dispatchers.Default) {
            async {
                //pretend this is a big network call
                for (i in 1..10) {
                    println("$TAG $i")
                    delay(1_000)
                    if(i == 3)
                        freezeHere() //dead pause, do not go any further
                }
            }
        }
    }

Następnie gdzieś indziej, na przykład w onResume:

override fun onResume() {
        super.onResume()
        unFreeze()
    }

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

j2emanue
źródło
4

Ponieważ istnieje już wiele dobrych odpowiedzi, chciałbym zamieścić prostszy przykład dla innych.

Przypadek użycia runBlocking :

  • myMethod () jest suspendfunkcją
  • runBlocking { }uruchamia Coroutine w sposób blokujący. Jest to podobne do tego, jak blokowaliśmy normalne wątki Threadklasą 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 {})

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
        runBlocking {
            Log.d(TAG,"Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
            myMethod();
        }
        Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
    }
    
    private suspend fun myMethod() {
        withContext(Dispatchers.Default) {
        for(i in 1..5) {
            Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
        }
    }

To daje:

I/TAG: Outer code started on Thread : main
D/TAG: Inner code started  on Thread : main making outer code suspend
// ---- main thread blocked here, it will wait until coroutine gets completed ----
D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- main thread resumes as coroutine is completed ----
I/TAG: Outer code resumed on Thread : main

uruchomienie przypadku użycia:

  • launch { } uruchamia program jednocześnie.
  • Oznacza to, że kiedy określimy uruchomienie, program rozpocznie wykonywanie w workerwątku.
  • workerGwintu i gwint zewnętrzny (z których nazywa launch { }), zarówno biegnie równolegle. Wewnętrznie maszyna JVM może wykonywać zapobiegawcze tworzenie wątków
  • Gdy wymagamy równoległego wykonywania wielu zadań, możemy to wykorzystać. Istnieją, scopesktóre określają czas życia programu. Jeśli określimy GlobalScope, program będzie działał do końca życia aplikacji.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
    
        GlobalScope.launch(Dispatchers.Default) {
            Log.d(TAG,"Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
            myMethod();
        }
        Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
    }
    
    private suspend fun myMethod() {
        withContext(Dispatchers.Default) {
            for(i in 1..5) {
                Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
            }
        }
    }

To daje:

10806-10806/com.example.viewmodelapp I/TAG: Outer code started on Thread : main
10806-10806/com.example.viewmodelapp I/TAG: Outer code resumed on Thread : main
// ---- In this example, main had only 2 lines to execute. So, worker thread logs start only after main thread logs complete
// ---- In some cases, where main has more work to do, the worker thread logs get overlap with main thread logs
10806-10858/com.example.viewmodelapp D/TAG: Inner code started  on Thread : DefaultDispatcher-worker-1 making outer code suspend
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-1

async i await przypadek użycia:

  • Kiedy mamy wiele zadań do wykonania i zależą one od wykonania przez innych asynci awaitpomogłyby.
  • Na przykład w poniższym kodzie znajdują się 2funkcje wstrzymania myMethod () i myMethod2 (). myMethod2()powinien zostać wykonany dopiero po całkowitym wypełnieniu myMethod() OR myMethod2() zależy od wyniku myMethod(), możemy użyć asynciawait
  • asyncuruchamia program równolegle podobny do launch. Zapewnia jednak sposób, aby poczekać na jeden program przed równoległym uruchomieniem innego programu.
  • Tak jest await(). asynczwraca wystąpienie Deffered<T>. Tbyłby Unitdomyślny. Kiedy musimy czekać na asynczakończenie, musimy odwołać .await()się Deffered<T>do tego async. Jak w poniższym przykładzie, wywołaliśmy, innerAsync.await()co oznacza, że ​​wykonanie zostanie zawieszone do czasu innerAsynczakończenia. To samo możemy zaobserwować w produkcji. innerAsyncZostanie zakończony pierwszy, który wzywa myMethod(). A potem async innerAsync2zaczyna się następny , który dzwonimyMethod2()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
    
         job = GlobalScope.launch(Dispatchers.Default) {
             innerAsync = async {
                 Log.d(TAG, "Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
                 myMethod();
             }
             innerAsync.await()
    
             innerAsync2 = async {
                 Log.w(TAG, "Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
                 myMethod2();
             }
        }
    
        Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
        }
    
    private suspend fun myMethod() {
        withContext(Dispatchers.Default) {
            for(i in 1..5) {
                Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
            }
        }
    }
    
    private suspend fun myMethod2() {
        withContext(Dispatchers.Default) {
            for(i in 1..10) {
                Log.w(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
            }
        }
    }

To daje:

11814-11814/? I/TAG: Outer code started on Thread : main
11814-11814/? I/TAG: Outer code resumed on Thread : main
11814-11845/? D/TAG: Inner code started  on Thread : DefaultDispatcher-worker-2 making outer code suspend
11814-11845/? D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- Due to await() call, innerAsync2 will start only after innerAsync gets completed
11814-11848/? W/TAG: Inner code started  on Thread : DefaultDispatcher-worker-4 making outer code suspend
11814-11848/? W/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 6 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 7 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 8 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 9 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 10 on Thread : DefaultDispatcher-worker-4
Kushal
źródło