Sự khác biệt giữa khởi chạy / tham gia và async / await trong các coroutines của Kotlin


Câu trả lời:


232
  • launchđược sử dụng để bắn và quên coroutine . Nó giống như bắt đầu một chủ đề mới. Nếu mã bên trong launchchấm dứt với ngoại lệ, sau đó nó được coi như còn tự do ngoại lệ trong một thread - thường được in để stderr trong backend ứng dụng JVM và treo các ứng dụng Android. joinđược sử dụng để chờ hoàn thành coroutine được phóng và nó không tuyên truyền ngoại lệ của nó. Tuy nhiên, một đứa trẻ bị tai nạn coroutine cũng hủy bỏ cha mẹ của nó với ngoại lệ tương ứng.

  • asyncđược sử dụng để bắt đầu một coroutine tính toán một số kết quả . Kết quả được đại diện bởi một thể hiện Deferredvà bạn phải sử dụng awaitnó. Một ngoại lệ chưa asyncđược lưu trong mã được lưu trữ bên trong kết quả Deferredvà không được gửi ở bất kỳ nơi nào khác, nó sẽ bị hủy bỏ âm thầm trừ khi được xử lý. Bạn KHÔNG được quên về coroutine bạn đã bắt đầu với async .


1
Async có phải là trình xây dựng coroutine phù hợp cho các cuộc gọi mạng trong Android không?
Faraaz

Trình xây dựng coroutine đúng phụ thuộc vào những gì bạn đang cố gắng thực hiện
Roman Elizarov

9
Bạn có thể nói rõ hơn về "Bạn KHÔNG PHẢI quên đi coroutine mà bạn đã bắt đầu với async" không? Có những vấn đề mà người ta không mong đợi chẳng hạn?
Luis

2
"Một ngoại lệ chưa được lưu trong mã async được lưu trữ bên trong Trì hoãn kết quả và không được gửi ở bất kỳ nơi nào khác, nó sẽ bị hủy bỏ âm thầm trừ khi được xử lý."
La Mã Elizarov

9
Nếu bạn quên kết quả của async thì nó sẽ kết thúc và sẽ được thu gom rác. Tuy nhiên, nếu nó gặp sự cố do một số lỗi trong mã của bạn, bạn sẽ không bao giờ tìm hiểu về điều đó. Đó là lý do tại sao.
La Mã Elizarov

77

Tôi thấy hướng dẫn này https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md sẽ hữu ích. Tôi sẽ trích dẫn những phần thiết yếu

🦄 coroutine

Về cơ bản, coroutines là chủ đề trọng lượng nhẹ.

Vì vậy, bạn có thể nghĩ về coroutine như một cái gì đó quản lý luồng theo cách rất hiệu quả.

Ra mắt

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
}

Vì vậy, launchbắt đầu một chủ đề nền, làm một cái gì đó và trả lại mã thông báo ngay lập tức Job. Bạn có thể gọi joinJobđể chặn cho đến khi launchchủ đề này hoàn thành

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

Về mặt khái niệm, async giống như khởi chạy. Nó bắt đầu một coroutine riêng biệt, đó là một sợi có trọng lượng nhẹ, hoạt động đồng thời với tất cả các coroutine khác. Sự khác biệt là việc khởi chạy trả về một Công việc và không mang bất kỳ giá trị kết quả nào, trong khi async trả về Trì hoãn - một tương lai không chặn trọng lượng nhẹ thể hiện lời hứa sẽ cung cấp kết quả sau này.

Vì vậy, asyncbắt đầu một chủ đề nền, làm một cái gì đó và trả lại mã thông báo ngay lập tức 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")
}

Bạn có thể sử dụng .await () trên giá trị hoãn lại để nhận kết quả cuối cùng của nó, nhưng Trì hoãn cũng là một Công việc, vì vậy bạn có thể hủy nó nếu cần.

Vì vậy, Deferredthực sự là một Job. Xem https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 async là háo hức theo mặc định

Có một tùy chọn lười biếng để không đồng bộ bằng cách sử dụng tham số bắt đầu tùy chọn với giá trị CoroutineStart.LAZY. Nó chỉ bắt đầu coroutine khi cần một số kết quả hoặc nếu một chức năng bắt đầu được gọi.


12

launchasyncđược sử dụng để bắt đầu các coroutines mới. Nhưng, họ thực hiện chúng theo cách khác nhau.

Tôi muốn đưa ra ví dụ rất cơ bản sẽ giúp bạn hiểu sự khác biệt rất dễ dàng

  1. phóng
    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;
    }
}

Trong ví dụ này, mã của tôi đang tải xuống 3 dữ liệu khi nhấp vào btnCountnút và hiển thị pgBarthanh tiến trình cho đến khi tất cả quá trình tải xuống hoàn tất. Có 3 suspendchức năng downloadTask1(), downloadTask2()downloadTask3()tải dữ liệu. Để mô phỏng nó, tôi đã sử dụng delay()các chức năng này. Các chức năng này chờ đợi 5 seconds, 8 seconds5 secondstương ứng.

Vì chúng ta đã sử dụng launchđể bắt đầu các hàm tạm ngưng này, launchsẽ thực hiện chúng một cách tuần tự (từng cái một) . Điều này có nghĩa là, downloadTask2()sẽ bắt đầu sau khi downloadTask1()được hoàn thành và downloadTask3()sẽ chỉ bắt đầu sau khi downloadTask2()hoàn thành.

Như trong ảnh chụp màn hình đầu ra Toast, tổng thời gian thực hiện để hoàn thành cả 3 lần tải xuống sẽ dẫn đến 5 giây + 8 giây + 5 giây = 18 giây vớilaunch

Ví dụ khởi chạy

  1. không đồng bộ

Như chúng ta đã thấy điều đó launchthực hiện sequentiallycho cả 3 nhiệm vụ. Thời gian để hoàn thành tất cả các nhiệm vụ là 18 seconds.

Nếu các tác vụ đó độc lập và nếu chúng không cần kết quả tính toán của tác vụ khác, chúng tôi có thể làm cho chúng chạy concurrently. Họ sẽ bắt đầu cùng một lúc và chạy đồng thời trong nền. Điều này có thể được thực hiện với async.

asynctrả về một thể hiện của Deffered<T>kiểu, trong đó Tloại dữ liệu mà hàm treo của chúng ta trả về. Ví dụ,

  • downloadTask1()sẽ trả về Deferred<String>vì String là kiểu trả về của hàm
  • downloadTask2()sẽ trả về Deferred<Int>vì Int là kiểu trả về của hàm
  • downloadTask3()sẽ trả về Deferred<Float>vì Float là kiểu trả về của hàm

Chúng ta có thể sử dụng đối tượng trả về từ asyncloại Deferred<T>để lấy giá trị trả về trong Tloại. Điều đó có thể được thực hiện với await()cuộc gọi. Kiểm tra mã dưới đây chẳng hạn

        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
        }

Bằng cách này, chúng tôi đã đưa ra cả 3 nhiệm vụ đồng thời. Vì vậy, tổng thời gian thực hiện của tôi để hoàn thành sẽ chỉ 8 secondslà thời gian downloadTask2()vì nó là lớn nhất trong cả 3 nhiệm vụ. Bạn có thể thấy điều này trong ảnh chụp màn hình sau trongToast message

ví dụ đang chờ


1
Cám ơn nhắc đến đó launchlà để tuần tự funs, trong khi asynccho đồng thời
Akbolat SSS

Bạn đã sử dụng khởi chạy một lần cho tất cả các tác vụ và không đồng bộ cho từng tác vụ. Có lẽ nó nhanh hơn bởi vì mỗi cái được phóng trong một coroutine khác và không đợi ai đó? Đây là một so sánh không chính xác. Thông thường hiệu suất là như nhau. Một điểm khác biệt chính là việc khởi chạy luôn bắt đầu một coroutine mới thay vì async chia tách chủ sở hữu. Một yếu tố nữa là nếu một trong những nhiệm vụ không đồng bộ sẽ thất bại vì một lý do thì coroutine cha cũng sẽ thất bại. Đó là lý do tại sao async không phổ biến như khởi chạy.
p2lem8dev

1
Câu trả lời này là không đúng, so sánh trực tiếp async với chức năng tạm ngưng thay vì khởi chạy. Thay vì gọi hàm đình chỉ trực tiếp trong ví dụ, nếu bạn gọi launch (Dispatchers.IO) {downloadTask1 ()} bạn sẽ thấy cả hai được thực thi đồng thời, không tuần tự , bạn sẽ không thể nhận được kết quả đầu ra nhưng bạn sẽ thấy rằng đó là không tuần tự. Ngoài ra, nếu bạn không ghép nối deferred.await () và gọi deferred.await () một cách riêng biệt, bạn sẽ thấy async là tuần tự.
Thracian

2
-1 điều này hoàn toàn sai. Cả hai launchasyncsẽ bắt đầu các coroutines mới. Bạn đang so sánh một coroutine không có con với một coroutine với 3 con. Bạn có thể thay thế từng asyncyêu cầu launchvà hoàn toàn không có gì thay đổi liên quan đến đồng thời.
Moira

Tiếng ồn bên ngoài trong câu trả lời này là thêm sự phức tạp nằm ngoài chủ đề đồng quy.
Truthadjustr

6
  1. cả hai trình xây dựng coroutine cụ thể là launch và async về cơ bản là lambdas với bộ thu kiểu CoroutineScope, có nghĩa là khối bên trong của chúng được biên dịch dưới dạng hàm treo, do đó cả hai đều chạy trong chế độ không đồng bộ VÀ cả hai sẽ thực hiện tuần tự khối của chúng.

  2. Sự khác biệt giữa khởi chạy và không đồng bộ là chúng cho phép hai khả năng khác nhau. Trình xây dựng khởi chạy trả về một Công việc tuy nhiên chức năng async sẽ trả về một đối tượng Trì hoãn. Bạn có thể sử dụng launch để thực thi một khối mà bạn không mong đợi bất kỳ giá trị nào được trả về từ nó, tức là ghi vào cơ sở dữ liệu hoặc lưu tệp hoặc xử lý một cái gì đó về cơ bản chỉ được gọi là tác dụng phụ của nó. Mặt khác, async trả về Trì hoãn như tôi đã nói trước đó trả về một giá trị hữu ích từ việc thực thi khối của nó, một đối tượng bao bọc dữ liệu của bạn, do đó bạn có thể sử dụng nó cho kết quả chủ yếu nhưng cũng có thể cho tác dụng phụ của nó. Lưu ý: bạn có thể loại bỏ phần hoãn lại và nhận giá trị của nó bằng cách sử dụng hàm chờ, điều này sẽ chặn việc thực thi các câu lệnh của bạn cho đến khi một giá trị được trả về hoặc một ngoại lệ được đưa ra!

  3. cả trình xây dựng coroutine (khởi chạy và không đồng bộ) đều có thể hủy được.

  4. Còn gì nữa không ?: vâng với khởi chạy nếu một ngoại lệ được ném trong khối của nó, coroutine sẽ tự động bị hủy và các ngoại lệ được gửi. Mặt khác, nếu điều đó xảy ra với async, ngoại lệ sẽ không được truyền bá thêm nữa và nên được bắt / xử lý trong đối tượng Trì hoãn được trả lại.

  5. thêm về coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1


1
Cảm ơn vì nhận xét này. Nó thu thập tất cả các điểm của chủ đề. Tôi muốn nói thêm rằng không phải tất cả các lần phóng đều bị hủy, ví dụ như nguyên tử không thể bị hủy bao giờ.
p2lem8dev

4

khởi động trả lại một công việc

async trả về một kết quả (công việc hoãn lại)

khởi chạy với phép nối được sử dụng để đợi cho đến khi công việc kết thúc. Nó chỉ đơn giản là tạm dừng lệnh gọi coroutine (), để luồng hiện tại tự do thực hiện các công việc khác (như thực hiện một coroutine khác) trong thời gian đó.

async được sử dụng để tính toán một số kết quả. Nó tạo ra một coroutine và trả về kết quả trong tương lai của nó như là một triển khai của Trì hoãn. Coroutine đang chạy bị hủy khi kết quả hoãn lại bị hủy.

Hãy xem xét một phương thức async trả về giá trị chuỗi. Nếu phương thức async được sử dụng mà không chờ đợi, nó sẽ trả về một chuỗi Trì hoãn nhưng nếu await được sử dụng, bạn sẽ nhận được một chuỗi như kết quả

Sự khác biệt chính giữa async và khởi chạy. Trì hoãn trả về một giá trị cụ thể của loại T sau khi Coroutine của bạn kết thúc thực thi, trong khi Công việc thì không.


0

Async vs Launch Async vs Launch Diff Image

khởi chạy / không đồng bộ

  • Sử dụng khi không cần kết quả,
  • Đừng chặn mã nơi được gọi,
  • Chạy song song

không đồng bộ cho kết quả

  • Khi bạn cần đợi kết quả và có thể chạy song song để đạt hiệu quả
  • Chặn mã nơi được gọi
  • chạy song song
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.