Apa perbedaan antara launch / join dan async / menanti di Kotlin coroutine

156

Di kotlinx.coroutinesperpustakaan Anda dapat memulai coroutine baru menggunakan launch(dengan join) atau async(dengan await). Apa perbedaan di antara mereka?

Roman Elizarov
sumber

Jawaban:

232
  • launchdigunakan untuk menembak dan melupakan coroutine . Ini seperti memulai utas baru. Jika kode di dalam launchterminal berakhir dengan pengecualian, maka kode tersebut diperlakukan seperti pengecualian yang tidak tertangkap di utas - biasanya dicetak ke stderr di aplikasi JVM backend dan aplikasi Android rusak. joindigunakan untuk menunggu penyelesaian coroutine yang diluncurkan dan tidak menyebarkan pengecualiannya. Namun, coroutine anak yang crash membatalkan induknya dengan pengecualian yang sesuai juga.

  • asyncdigunakan untuk memulai coroutine yang menghitung beberapa hasil . Hasilnya diwakili oleh instance Deferreddan Anda harus menggunakannya await. Pengecualian tanpa tertangkap di dalam asynckode disimpan di dalam hasil Deferreddan tidak dikirim di tempat lain, itu akan diam-diam dijatuhkan kecuali diproses. Anda TIDAK HARUS melupakan coroutine yang sudah Anda mulai dengan async .

Roman Elizarov
sumber
1
Apakah Async pembangun coroutine yang tepat untuk panggilan jaringan di Android?
Faraaz
Pembangun coroutine yang tepat tergantung pada apa yang Anda coba capai
Roman Elizarov
9
Bisakah Anda menguraikan "Anda TIDAK HARUS melupakan coroutine yang sudah Anda mulai dengan async"? Apakah ada Gotcha yang tidak diharapkan misalnya?
Luis
2
"Pengecualian tanpa tertangkap di dalam kode async disimpan di dalam Ditangguhkan yang dihasilkan dan tidak dikirim ke tempat lain, itu akan secara diam-diam dijatuhkan kecuali diproses."
Roman Elizarov
9
Jika Anda lupa hasil async maka akan selesai dan akan menjadi sampah yang dikumpulkan. Namun, jika crash karena beberapa bug dalam kode Anda, Anda tidak akan pernah mempelajarinya. Itulah mengapa.
Roman Elizarov
77

Saya menemukan panduan ini https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md berguna. Saya akan mengutip bagian-bagian penting

🦄 coroutine

Intinya, coroutine adalah benang yang ringan.

Jadi Anda bisa menganggap coroutine sebagai sesuatu yang mengelola thread dengan cara yang sangat efisien.

🐤 diluncurkan

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
}

Jadi mulailah launchutas latar belakang, lakukan sesuatu, dan kembalikan token segera sebagai Job. Anda dapat memanggil joinini Jobuntuk memblokir sampai launchutas ini selesai

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

Secara konseptual, async seperti peluncuran. Ini memulai coroutine terpisah yang merupakan benang ringan yang bekerja bersamaan dengan semua coroutine lainnya. Perbedaannya adalah bahwa peluncuran mengembalikan suatu Pekerjaan dan tidak membawa nilai yang dihasilkan, sementara async mengembalikan yang Ditangguhkan - masa depan ringan tanpa pemblokiran yang mewakili janji untuk memberikan hasil nanti.

Jadi mulailah asyncutas latar belakang, lakukan sesuatu, dan kembalikan token segera sebagai 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")
}

Anda dapat menggunakan .await () pada nilai yang ditangguhkan untuk mendapatkan hasil akhirnya, tetapi Ditangguhkan juga merupakan Pekerjaan, sehingga Anda dapat membatalkannya jika diperlukan.

Jadi Deferredsebenarnya a Job. Lihat https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 async sangat ingin secara default

Ada opsi kemalasan untuk async menggunakan parameter awal opsional dengan nilai CoroutineStart.LAZY. Itu mulai coroutine hanya ketika hasilnya diperlukan oleh beberapa menunggu atau jika fungsi mulai dipanggil.

onmyway133
sumber
12

launchdan asyncdigunakan untuk memulai coroutine baru. Tapi, mereka mengeksekusinya dengan cara yang berbeda.

Saya ingin menunjukkan contoh yang sangat mendasar yang akan membantu Anda memahami perbedaan dengan sangat mudah

  1. meluncurkan
    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;
    }
}

Dalam contoh ini, kode saya mengunduh 3 data dengan mengklik btnCounttombol dan menampilkan pgBarbilah kemajuan hingga semua unduhan selesai. Ada 3 suspendfungsi downloadTask1(), downloadTask2()dan downloadTask3()yang mengunduh data. Untuk mensimulasikannya, saya telah menggunakan delay()fungsi-fungsi ini. Fungsi-fungsi ini menunggu 5 seconds, 8 secondsdan 5 secondsmasing - masing.

Seperti yang telah kita gunakan launchuntuk memulai fungsi-fungsi ini, launchakan menjalankannya secara berurutan (satu-per-satu) . Ini berarti, downloadTask2()akan mulai setelah downloadTask1()selesai dan downloadTask3()akan mulai hanya setelah downloadTask2()selesai.

Seperti dalam output screenshot Toast, total waktu eksekusi untuk menyelesaikan semua 3 download akan menyebabkan 5 detik + 8 detik + 5 detik = 18 detik denganlaunch

Luncurkan Contoh

  1. async

Seperti yang kita lihat, ini launchmembuat eksekusi sequentiallyuntuk semua 3 tugas. Waktu untuk menyelesaikan semua tugas adalah 18 seconds.

Jika tugas-tugas itu independen dan jika mereka tidak membutuhkan hasil perhitungan tugas lain, kita dapat membuatnya berjalan concurrently. Mereka akan mulai pada saat yang sama dan berjalan bersamaan di latar belakang. Ini bisa dilakukan dengan async.

asyncmengembalikan sebuah instance dari Deffered<T>tipe, di mana Tadalah tipe data yang mengembalikan fungsi menangguhkan kami. Sebagai contoh,

  • downloadTask1()akan kembali Deferred<String>karena String adalah jenis kembali fungsi
  • downloadTask2()akan kembali Deferred<Int>karena Int adalah jenis pengembalian fungsi
  • downloadTask3()akan kembali Deferred<Float>karena Float adalah tipe pengembalian fungsi

Kita bisa menggunakan objek kembali asyncdari tipe Deferred<T>untuk mendapatkan nilai yang dikembalikan dalam Ttipe. Itu bisa dilakukan dengan await()panggilan. Periksa kode di bawah ini misalnya

        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
        }

Dengan cara ini, kami telah meluncurkan ketiga tugas secara bersamaan. Jadi, total waktu eksekusi saya untuk menyelesaikan hanya akan menjadi 8 secondswaktu karena downloadTask2()itu adalah yang terbesar dari semua 3 tugas. Anda dapat melihat ini di screenshot berikut diToast message

menunggu contoh

Kushal
sumber
1
Terima kasih telah menyebutkan bahwa launchini adalah untuk async
bersenang
Anda telah menggunakan peluncuran sekali untuk semua tugas dan async untuk masing-masing. Mungkin lebih cepat karena masing-masing diluncurkan di coroutine lain dan tidak menunggu seseorang? Ini perbandingan yang salah. Biasanya kinerjanya sama. Salah satu perbedaan utama adalah bahwa peluncuran selalu memulai coroutine baru, bukan async yang membagi pemilik. Satu faktor lagi adalah bahwa jika salah satu tugas async akan gagal karena suatu alasan induk coroutine akan gagal juga. Itu sebabnya async tidak sepopuler peluncuran.
p2lem8dev
1
Jawaban ini tidak benar, membandingkan async dengan fungsi menangguhkan secara langsung alih-alih diluncurkan. Alih-alih memanggil fungsi menangguhkan secara langsung dalam contoh, jika Anda memanggil peluncuran (Dispatchers.IO) {downloadTask1 ()} Anda akan melihat bahwa keduanya dieksekusi secara bersamaan, tidak secara berurutan , Anda tidak akan bisa mendapatkan output tetapi Anda akan melihat bahwa itu adalah tidak berurutan. Juga jika Anda tidak menggabungkan deferred.await () dan memanggil deferred.await () secara terpisah, Anda akan melihat bahwa async berurutan.
Thracian
2
-1 ini salah besar. Keduanya launchdan asyncakan memulai coroutine baru. Anda membandingkan coroutine tunggal tanpa anak dengan coroutine tunggal dengan 3 anak. Anda dapat mengganti setiap asyncdoa dengan launchdan sama sekali tidak ada yang berubah sehubungan dengan konkurensi.
Moira
Suara asing dalam jawaban ini menambah kompleksitas yang berada di luar topik rutin bersama.
truthadjustr
6
  1. kedua pembangun coroutine yaitu launch dan async pada dasarnya adalah lambdas dengan penerima tipe CoroutineScope yang berarti blok bagian dalamnya dikompilasi sebagai fungsi menangguhkan, oleh karena itu mereka berdua berjalan dalam mode asinkron dan mereka berdua akan menjalankan blok mereka secara berurutan.

  2. Perbedaan antara peluncuran dan async adalah mereka memungkinkan dua kemungkinan yang berbeda. Pembuat peluncuran mengembalikan suatu Pekerjaan namun fungsi async akan mengembalikan objek yang Ditangguhkan. Anda dapat menggunakan launch untuk mengeksekusi blok yang tidak Anda harapkan nilai yang dikembalikan darinya yaitu menulis ke database atau menyimpan file atau memproses sesuatu yang pada dasarnya hanya dipanggil untuk efek sampingnya. Di sisi lain, async yang mengembalikan Deferred seperti yang saya nyatakan sebelumnya mengembalikan nilai yang berguna dari eksekusi bloknya, sebuah objek yang membungkus data Anda, sehingga Anda dapat menggunakannya terutama untuk hasilnya tetapi juga untuk efek sampingnya. NB: Anda dapat menghapus yang ditangguhkan dan mendapatkan nilainya menggunakan fungsi menunggu, yang akan memblokir eksekusi laporan Anda sampai nilai dikembalikan atau pengecualian dilemparkan!

  3. pembangun coroutine (launch dan async) dapat dibatalkan.

  4. apa lagi ?: ya dengan peluncuran jika pengecualian dilemparkan di dalam bloknya, coroutine secara otomatis dibatalkan dan pengecualian dikirimkan. Di sisi lain, jika itu terjadi dengan async pengecualian tidak diperbanyak lebih lanjut dan harus ditangkap / ditangani dalam objek yang ditangguhkan yang dikembalikan.

  5. lebih lanjut tentang coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1

AouledIssa
sumber
1
Terima kasih atas komentar ini. Itu mengumpulkan semua poin dari utas. Saya akan menambahkan bahwa tidak semua peluncuran dibatalkan misalnya Atom tidak dapat dibatalkan.
p2lem8dev
4

Peluncuran mengembalikan pekerjaan

async mengembalikan hasil (pekerjaan yang ditangguhkan)

peluncuran dengan bergabung digunakan untuk menunggu sampai pekerjaan selesai. Itu hanya menunda panggilan bergabung coroutine (), meninggalkan utas saat ini bebas untuk melakukan pekerjaan lain (seperti menjalankan coroutine lain) sementara itu.

async digunakan untuk menghitung beberapa hasil. Itu menciptakan coroutine dan mengembalikan hasil masa depannya sebagai implementasi dari Ditangguhkan. Coroutine yang berjalan dibatalkan ketika hasil yang ditangguhkan dibatalkan.

Pertimbangkan metode async yang mengembalikan nilai string. Jika metode async digunakan tanpa menunggu itu akan mengembalikan string yang ditangguhkan tetapi jika menunggu digunakan Anda akan mendapatkan string sebagai hasilnya

Perbedaan utama antara async dan peluncuran. Deferred mengembalikan nilai tipe T tertentu setelah Coroutine Anda selesai dieksekusi, sedangkan Job tidak.

Batas
sumber
0

Async vs Launch Async vs Launch Diff Image

luncurkan / async tidak ada hasil

  • Gunakan saat tidak membutuhkan hasil,
  • Jangan blokir kode tempat dipanggil,
  • Jalankan secara paralel

async untuk hasil

  • Ketika Anda perlu menunggu hasilnya dan dapat berjalan secara paralel untuk efisiensi
  • Blokir kode tempat dipanggil
  • berjalan secara paralel
Vinod Kamble
sumber