W kotlinx.coroutines
bibliotece możesz uruchomić nowy program, używając launch
(zjoin
) lub async
(z await
). Jaka jest różnica między nimi?
W kotlinx.coroutines
bibliotece możesz uruchomić nowy program, używając launch
(zjoin
) lub async
(z await
). Jaka jest różnica między nimi?
Odpowiedzi:
launch
służy do odpalania i zapomnienia programu . To jak rozpoczęcie nowego wątku. Jeśli kod wewnątrz launch
koń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. join
sł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.
async
służy do uruchamiania programu, który oblicza jakiś wynik . Wynik jest reprezentowany przez instancję Deferred
i należyawait
na niej użyć . Nieprzechwycony wyjątek w async
kodzie jest przechowywany w wynikowym Deferred
i 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 .
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 launch
uruchamia wątek w tle, robi coś i natychmiast zwraca token jako Job
. Możesz wywołać join
to, Job
aby zablokować do launch
zakoń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 async
uruchamia 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 Deferred
naprawdę 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.
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ę
- 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 btnCount
przycisku i wyświetla pgBar
pasek postępu do zakończenia pobierania. Istnieją 3 suspend
funkcje downloadTask1()
, downloadTask2()
i downloadTask3()
które pobiera dane. Aby to zasymulować, użyłem delay()
w tych funkcjach. Funkcje te czeka 5 seconds
, 8 seconds
i5 seconds
odpowiednio.
Tak jak używaliśmy launch
do uruchamiania tych funkcji wstrzymania, launch
bę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
- async
Jak widzieliśmy, launch
powoduje to wykonanie sequentially
wszystkich 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
.
async
zwraca instancję Deffered<T>
typu, gdzie T
jest typem danych zwracanych przez naszą funkcję zawieszenia. Na przykład,
downloadTask1()
zwróci, Deferred<String>
ponieważ String jest zwracanym typem funkcjidownloadTask2()
zwróci, Deferred<Int>
ponieważ Int jest zwracanym typem funkcjidownloadTask3()
zwróci, Deferred<Float>
ponieważ Float jest zwracanym typem funkcjiMożemy użyć zwracanego obiektu from async
typu, Deferred<T>
aby otrzymać zwróconą wartość w T
typie. 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 seconds
który jest czas, downloadTask2()
ponieważ jest to największe ze wszystkich trzech zadań. Możesz to zobaczyć na poniższym zrzucie ekranu wToast message
launch
jest dla kolejnych miłośników, natomiast async
na równoczesną
launch
i async
rozpoczną nowe programy. Porównujesz pojedynczy program bez dzieci z jednym programem z trojgiem dzieci. Możesz zamienić każde z async
wywołań na launch
i absolutnie nic by się nie zmieniło w odniesieniu do współbieżności.
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.
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!
oba narzędzia do tworzenia programów (uruchamianie i asynchronizacja) można anulować.
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.
więcej na temat coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1
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.
Async vs Launch Async vs Launch Diff Image
uruchomienie / asynchronizacja bez wyniku
asynchroniczne dla wyniku