Jaka jest różnica między launch / join i async / await w programach Kotlin


Odpowiedzi:


232
  • launchsłuży do odpalania i zapomnienia programu . To jak rozpoczęcie nowego wątku. Jeśli kod wewnątrz launchkończy się z wyjątkiem, jest traktowany jak nieprzechwycony wyjątek w wątku - zwykle jest drukowany na stderr w aplikacjach JVM zaplecza i powoduje awarię aplikacji na Androida. joinsłuży do oczekiwania na zakończenie uruchomionego programu i nie propaguje swojego wyjątku. Jednak uszkodzony program podrzędny anuluje również swojego rodzica z odpowiednim wyjątkiem.

  • asyncsłuży do uruchamiania programu, który oblicza jakiś wynik . Wynik jest reprezentowany przez instancję Deferredi należyawait na niej użyć . Nieprzechwycony wyjątek w asynckodzie jest przechowywany w wynikowym Deferredi nie jest dostarczany nigdzie indziej, zostanie po cichu usunięty, chyba że zostanie przetworzony. NIE WOLNO zapominać o programie, który zacząłeś z async .


1
czy Async to właściwy kreator coroutine do połączeń sieciowych w systemie Android?
Faraaz

Wybór odpowiedniego kreatora programów zależy od tego, co próbujesz osiągnąć
Roman Elizarov

9
Czy możesz rozwinąć temat „NIE WOLNO zapomnieć o programie asynchronicznym, z którym zacząłeś”? Czy są jakieś problemy, których nie można by się spodziewać na przykład?
Luis

2
„Nieprzechwycony wyjątek wewnątrz kodu asynchronicznego jest przechowywany w wynikowym odroczeniu i nie jest dostarczany nigdzie indziej, zostanie po cichu usunięty, chyba że zostanie przetworzony”.
Roman Elizarov,

9
Jeśli zapomnisz wyniku asynchronizacji, zakończy się i zostanie usunięty. Jeśli jednak ulegnie awarii z powodu jakiegoś błędu w kodzie, nigdy się o tym nie dowiesz. Dlatego.
Roman Elizarov

77

Uważam, że ten przewodnik https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md jest przydatny. Zacytuję najważniejsze części

🦄 coroutine

Zasadniczo korutyny są lekkimi nitkami.

Możesz więc myśleć o programie coroutine jako o czymś, co zarządza wątkiem w bardzo wydajny sposób.

🐤 uruchomienie

fun main(args: Array<String>) {
    launch { // launch new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello,") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

Więc launchuruchamia wątek w tle, robi coś i natychmiast zwraca token jako Job. Możesz wywołać jointo, Jobaby zablokować do launchzakończenia tego wątku

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch { // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join() // wait until child coroutine completes
}

🦆 async

Koncepcyjnie async jest jak uruchomienie. Uruchamia osobny program, który jest lekkim wątkiem, który działa równolegle ze wszystkimi innymi programami. Różnica polega na tym, że launch zwraca zadanie i nie przenosi żadnej wynikowej wartości, podczas gdy async zwraca Deferred - lekką, nieblokującą przyszłość, która reprezentuje obietnicę dostarczenia wyniku później.

Więc asyncuruchamia wątek w tle, robi coś i natychmiast zwraca token jako Deferred.

fun main(args: Array<String>) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}

Możesz użyć .await () na odroczonej wartości, aby uzyskać jej ostateczny wynik, ale Odroczone jest również zadaniem, więc możesz je anulować w razie potrzeby.

Tak Deferrednaprawdę jest Job. Zobacz https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 asynchronizacja jest domyślnie gorsza

Istnieje opcja lenistwa do asynchronizacji przy użyciu opcjonalnego parametru startu z wartością CoroutineStart.LAZY. Uruchamia program tylko wtedy, gdy jego wynik jest potrzebny przez jakiś await lub gdy wywoływana jest funkcja start.


11

launch i async są używane do uruchamiania nowych programów. Ale wykonują je w inny sposób.

Chciałbym pokazać bardzo podstawowy przykład, który pomoże ci bardzo łatwo zrozumieć różnicę

  1. uruchomić
    class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnCount.setOnClickListener {
            pgBar.visibility = View.VISIBLE
            CoroutineScope(Dispatchers.Main).launch {
                val currentMillis = System.currentTimeMillis()
                val retVal1 = downloadTask1()
                val retVal2 = downloadTask2()
                val retVal3 = downloadTask3()
                Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1}, ${retVal2}, ${retVal3} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
                pgBar.visibility = View.GONE
            }
        }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask1() : String {
        kotlinx.coroutines.delay(5000);
        return "Complete";
    }

    // Task 1 will take 8 seconds to complete download    
    private suspend fun downloadTask2() : Int {
        kotlinx.coroutines.delay(8000);
        return 100;
    }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask3() : Float {
        kotlinx.coroutines.delay(5000);
        return 4.0f;
    }
}

W tym przykładzie mój kod pobiera 3 dane po kliknięciu btnCountprzycisku i wyświetla pgBarpasek postępu do zakończenia pobierania. Istnieją 3 suspendfunkcje downloadTask1(), downloadTask2()i downloadTask3()które pobiera dane. Aby to zasymulować, użyłem delay()w tych funkcjach. Funkcje te czeka 5 seconds, 8 secondsi5 seconds odpowiednio.

Tak jak używaliśmy launchdo uruchamiania tych funkcji wstrzymania, launchbędzie wykonywać je sekwencyjnie (jedna po drugiej) . Oznacza to, downloadTask2()że zacznie się po downloadTask1()zakończeniu i downloadTask3()zacznie się dopiero podownloadTask2() zakończeniu.

Podobnie jak w przypadku zrzutu ekranu wyjściowego Toast, całkowity czas wykonania wszystkich 3 pobrań wyniósłby 5 sekund + 8 sekund + 5 sekund = 18 sekund zlaunch

Uruchom przykład

  1. async

Jak widzieliśmy, launchpowoduje to wykonanie sequentiallywszystkich 3 zadań. Nadszedł czas na wykonanie wszystkich zadań 18 seconds.

Jeśli te zadania są niezależne i nie wymagają wyniku obliczeń innego zadania, możemy je uruchomić concurrently. Uruchamiałyby się w tym samym czasie i działały równolegle w tle. Można to zrobić za pomocą async.

asynczwraca instancję Deffered<T>typu, gdzie Tjest typem danych zwracanych przez naszą funkcję zawieszenia. Na przykład,

  • downloadTask1()zwróci, Deferred<String>ponieważ String jest zwracanym typem funkcji
  • downloadTask2()zwróci, Deferred<Int>ponieważ Int jest zwracanym typem funkcji
  • downloadTask3()zwróci, Deferred<Float>ponieważ Float jest zwracanym typem funkcji

Możemy użyć zwracanego obiektu from asynctypu, Deferred<T>aby otrzymać zwróconą wartość w Ttypie. Można to zrobić await()telefonicznie. Sprawdź na przykład poniższy kod

        btnCount.setOnClickListener {
        pgBar.visibility = View.VISIBLE

        CoroutineScope(Dispatchers.Main).launch {
            val currentMillis = System.currentTimeMillis()
            val retVal1 = async(Dispatchers.IO) { downloadTask1() }
            val retVal2 = async(Dispatchers.IO) { downloadTask2() }
            val retVal3 = async(Dispatchers.IO) { downloadTask3() }

            Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1.await()}, ${retVal2.await()}, ${retVal3.await()} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
            pgBar.visibility = View.GONE
        }

W ten sposób uruchomiliśmy wszystkie 3 zadania jednocześnie. Tak więc mój całkowity czas wykonania do ukończenia byłby tylko tym, na 8 secondsktóry jest czas, downloadTask2()ponieważ jest to największe ze wszystkich trzech zadań. Możesz to zobaczyć na poniższym zrzucie ekranu wToast message

czekać na przykład


1
Dzięki za wspomnieć, że launchjest dla kolejnych miłośników, natomiast asyncna równoczesną
Akbolat SSS

Użyłeś uruchamiania raz dla wszystkich zadań i asynchronicznego dla każdego z nich. Może to szybciej, bo każdy został uruchomiony w innym programie i nie czeka na kogoś? To jest błędne porównanie. Zwykle wydajność jest taka sama. Jedną kluczową różnicą jest to, że uruchomienie zawsze rozpoczyna nowy program zamiast asynchronicznego, który dzieli właściciela. Jeszcze jednym czynnikiem jest to, że jeśli jedno z zadań asynchronicznych nie powiedzie się z jakiegoś powodu, program nadrzędny również nie powiedzie się. Dlatego asynchronizacja nie jest tak popularna jak uruchomienie.
p2lem8dev

1
Ta odpowiedź nie jest właściwa, porównując funkcje asynchroniczne z funkcjami wstrzymania bezpośrednio zamiast uruchamiania. Zamiast wywoływać funkcję suspend bezpośrednio w przykładzie, jeśli wywołasz funkcję launch (Dispatchers.IO) {downloadTask1 ()}, zobaczysz, że oba są wykonywane jednocześnie, a nie sekwencyjnie , nie będziesz w stanie uzyskać wyników, ale zobaczysz, że jest to nie sekwencyjne. Również jeśli nie połączysz deferred.await () i nie wywołasz osobno deferred.await (), zobaczysz, że async jest sekwencyjny.
Tracki

2
-1 to jest po prostu błędne. Obie launchi asyncrozpoczną nowe programy. Porównujesz pojedynczy program bez dzieci z jednym programem z trojgiem dzieci. Możesz zamienić każde z asyncwywołań na launchi absolutnie nic by się nie zmieniło w odniesieniu do współbieżności.
Moira

Obcy szum w tej odpowiedzi polega na dodaniu złożoności, która wykracza poza wspólny temat.
trueadjustr

6
  1. oba programy budujące programy, a mianowicie uruchamianie i asynchronizacja, są w zasadzie lambdami z odbiornikiem typu CoroutineScope, co oznacza, że ​​ich wewnętrzny blok jest kompilowany jako funkcja wstrzymania, dlatego oba działają w trybie asynchronicznym ORAZ obaj będą wykonywać swój blok sekwencyjnie.

  2. Różnica między uruchomieniem a asynchronizacją polega na tym, że umożliwiają one dwie różne możliwości. Kreator uruchamiania zwraca zadanie, jednak funkcja async zwróci obiekt odroczony. Możesz użyć uruchomienia, aby wykonać blok, którego nie oczekujesz od niego żadnej zwróconej wartości, tj. Zapisanie do bazy danych lub zapisanie pliku lub przetworzenie czegoś, co w zasadzie wywołało efekt uboczny. Z drugiej strony asynchronizacja, która zwraca Deferred, tak jak powiedziałem wcześniej, zwraca użyteczną wartość z wykonania swojego bloku, obiektu, który opakowuje twoje dane, więc możesz go użyć głównie do jego wyniku, ale być może również do efektu ubocznego. Uwaga: możesz usunąć deferred i uzyskać jego wartość za pomocą funkcji await, która będzie blokować wykonywanie twoich instrukcji do momentu zwrócenia wartości lub wyrzucenia wyjątków!

  3. oba narzędzia do tworzenia programów (uruchamianie i asynchronizacja) można anulować.

  4. cokolwiek więcej ?: tak z uruchomieniem, jeśli wyjątek zostanie zgłoszony w jego bloku, program zostanie automatycznie anulowany, a wyjątki zostaną dostarczone. Z drugiej strony, jeśli dzieje się tak w przypadku asynchronizacji, wyjątek nie jest dalej propagowany i powinien zostać przechwycony / obsłużony w zwróconym obiekcie Deferred.

  5. więcej na temat coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1


1
Dzięki za ten komentarz. Zebrał wszystkie punkty nici. Dodam, że nie wszystkie uruchomienia są anulowane, np. Atomic nie może być nigdy anulowany.
p2lem8dev

4

uruchomienie zwraca zadanie

async zwraca wynik (zadanie odroczone)

uruchomienie z łączeniem służy do czekania, aż zadanie zostanie zakończone. po prostu zawiesza wywołanie funkcji join (), pozostawiając bieżący wątek wolny do wykonywania w międzyczasie innych prac (takich jak wykonywanie innego programu).

async służy do obliczania niektórych wyników. Tworzy coroutine i zwraca jego przyszły wynik jako implementację Deferred. Uruchomiony program jest anulowany, gdy wynikowe odroczenie zostanie anulowane.

Rozważ metodę asynchroniczną, która zwraca wartość ciągu. Jeśli metoda async jest używana bez await, zwróci ona ciąg odroczony, ale jeśli zostanie użyta await, otrzymasz ciąg jako wynik

Kluczowa różnica między async a launch. Deferred zwraca określoną wartość typu T po zakończeniu wykonywania programu Coroutine, podczas gdy Job tego nie robi.


0

Async vs Launch Async vs Launch Diff Image

uruchomienie / asynchronizacja bez wyniku

  • Użyj, gdy nie potrzebujesz wyniku,
  • Nie blokuj kodu w miejscu wywołania,
  • Biegaj równolegle

asynchroniczne dla wyniku

  • Kiedy musisz poczekać na wynik i możesz działać równolegle dla wydajności
  • Zablokuj kod, w którym jest wywoływany
  • przebiegać równolegle
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.