อะไรคือความแตกต่างระหว่างการเปิดตัว / เข้าร่วมและ async / ที่รอใน Kotlin coroutines


คำตอบ:


232
  • launchจะใช้ในการยิงและลืม coroutine มันก็เหมือนกับการเริ่มหัวข้อใหม่ หากโค้ดภายในการlaunchยุติมีข้อยกเว้นจะถือว่าเป็นข้อยกเว้นที่ไม่ถูกตรวจจับในเธรดโดยปกติจะพิมพ์ไปที่ stderr ในแอปพลิเคชัน JVM แบ็กเอนด์และแอปพลิเคชัน Android ขัดข้อง joinถูกใช้เพื่อรอให้ coroutine ที่เปิดตัวเสร็จสมบูรณ์และไม่เผยแพร่ข้อยกเว้น อย่างไรก็ตามcoroutine เด็กที่ล้มเหลวจะยกเลิกพาเรนต์ด้วยข้อยกเว้นที่เกี่ยวข้องเช่นกัน

  • asyncจะใช้ในการเริ่มต้น coroutine ที่คำนวณผลบางอย่าง ผลลัพธ์แสดงโดยอินสแตนซ์ของDeferredและคุณต้องใช้awaitมัน ข้อยกเว้นที่ไม่ได้ตรวจสอบภายในasyncรหัสจะถูกเก็บไว้ในผลลัพธ์Deferredและจะไม่ถูกส่งไปที่อื่นมันจะถูกลบอย่างเงียบ ๆ ยกเว้นว่ามีการประมวลผล คุณต้องไม่ลืมเกี่ยวกับ coroutine ที่คุณได้เริ่มต้นด้วยการ async


1
Async เป็นผู้สร้าง coroutine ที่เหมาะสมสำหรับการโทรผ่านเครือข่ายใน Android หรือไม่?
Faraaz

ผู้สร้าง Coroutine ที่ถูกต้องนั้นขึ้นอยู่กับสิ่งที่คุณพยายามจะทำ
Roman Elizarov

9
คุณสามารถอธิบายรายละเอียดเกี่ยวกับ "คุณต้องไม่ลืมเกี่ยวกับ coroutine ที่คุณเริ่มต้นด้วย async"? มี gotchas ที่จะไม่คาดหวังเช่น?
ลูอิส

2
"ข้อยกเว้นที่ไม่ได้ตรวจสอบภายในรหัส async จะถูกเก็บไว้ภายใน Deferred ผลลัพธ์ที่ได้และจะไม่ถูกส่งไปที่อื่นมันจะลดลงอย่างเงียบ ๆ เว้นแต่จะถูกประมวลผล
Roman Elizarov

9
หากคุณลืมผลลัพธ์ของการซิงค์มากกว่าจะเสร็จสิ้นและจะถูกรวบรวมขยะ อย่างไรก็ตามหากเกิดปัญหาเนื่องจากข้อผิดพลาดบางอย่างในรหัสของคุณคุณจะไม่เรียนรู้เกี่ยวกับสิ่งนั้น นั่นคือเหตุผล
Roman Elizarov

77

ฉันพบคู่มือนี้https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.mdจะเป็นประโยชน์ ฉันจะอ้างอิงส่วนสำคัญ

🦄 coroutine

โดยพื้นฐานแล้ว coroutines เป็นเธรดที่มีน้ำหนักเบา

ดังนั้นคุณสามารถคิด coroutine เป็นสิ่งที่จัดการเธรดในวิธีที่มีประสิทธิภาพมาก

🐤 เปิดตัว

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
}

ดังนั้นlaunchเริ่มหัวข้อพื้นหลังทำอะไรและคืนโทเค็นทันทีJobเริ่มด้ายพื้นหลังไม่บางสิ่งบางอย่างและส่งกลับโทเค็นทันทีคุณสามารถเรียกjoinสิ่งนี้Jobเพื่อบล็อกจนกว่าlaunchเธรดนี้จะเสร็จสมบูรณ์

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

แนวคิด async เหมือนกับการเปิดตัว มันเริ่มต้น coroutine แยกซึ่งเป็นเธรดที่มีน้ำหนักเบาที่ทำงานพร้อมกันกับ coroutines อื่น ๆ ทั้งหมด ความแตกต่างคือการส่งคืนงานและไม่มีค่าผลลัพธ์ใด ๆ ในขณะที่ async ส่งคืน Deferred - อนาคตที่ไม่ปิดกั้นน้ำหนักเบาซึ่งแสดงถึงคำมั่นสัญญาที่จะให้ผลลัพธ์ในภายหลัง

ดังนั้นเริ่มด้ายพื้นหลังไม่บางสิ่งบางอย่างและส่งกลับโทเค็นทันทีasyncDeferred

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")
}

คุณสามารถใช้. aitit () กับค่าที่เลื่อนออกไปเพื่อรับผลลัพธ์ในที่สุด แต่ Deferred ยังเป็นงานดังนั้นคุณสามารถยกเลิกได้หากจำเป็น

ดังนั้นเป็นจริงDeferred Jobดูhttps://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 async กระตือรือร้นเป็นค่าเริ่มต้น

มีตัวเลือกความเกียจคร้านที่จะซิงค์โดยใช้พารามิเตอร์เริ่มต้นที่เป็นทางเลือกพร้อมค่าของ CoroutineStart.LAZY มันเริ่มต้น coroutine ก็ต่อเมื่อต้องการผลโดยบางคนรอหรือถ้าฟังก์ชั่นเริ่มต้นจะถูกเรียกใช้


11

launchและasyncถูกใช้เพื่อเริ่มต้น coroutines ใหม่ แต่พวกเขาดำเนินการในลักษณะที่แตกต่างกัน

ฉันต้องการแสดงตัวอย่างพื้นฐานที่จะช่วยให้คุณเข้าใจความแตกต่างได้ง่ายมาก

  1. เปิด
    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;
    }
}

ในตัวอย่างนี้รหัสของฉันกำลังดาวน์โหลดข้อมูล 3 ปุ่มคลิกbtnCountและแสดงpgBarแถบความคืบหน้าจนกว่าการดาวน์โหลดทั้งหมดจะเสร็จสมบูรณ์ มี 3 suspendฟังก์ชั่นdownloadTask1(), downloadTask2()และdownloadTask3()ข้อมูลการดาวน์โหลด เพื่อจำลองฉันใช้delay()ในฟังก์ชั่นเหล่านี้ ฟังก์ชั่นเหล่านี้รอให้5 seconds, 8 secondsและ5 secondsตามลำดับ

ในฐานะที่เราเคยใช้launchสำหรับการเริ่มต้นเหล่านี้ฟังก์ชั่นระงับlaunchจะดำเนินการให้ตามลำดับ (อย่างใดอย่างหนึ่งโดยหนึ่ง) ซึ่งหมายความว่าdownloadTask2()จะเริ่มหลังจากdownloadTask1()เสร็จสิ้นและdownloadTask3()จะเริ่มหลังจากdownloadTask2()เสร็จสิ้นเท่านั้น

เช่นเดียวกับในภาพหน้าจอเอาท์พุทToastเวลาดำเนินการทั้งหมดในการดาวน์โหลดทั้งหมด 3 ครั้งจะนำไปสู่5 วินาที + 8 วินาที + 5 วินาที = 18 วินาทีด้วยlaunch

เปิดตัวอย่าง

  1. async

อย่างที่เราเห็นนั่นlaunchทำให้การทำงานsequentiallyทั้ง 3 งาน 18 secondsเวลาที่จะเสร็จงานทั้งหมดได้

หากงานเหล่านั้นเป็นอิสระและหากพวกเขาไม่ต้องการผลลัพธ์การคำนวณของงานอื่นเราสามารถทำให้พวกเขาทำงานconcurrentlyได้ พวกเขาจะเริ่มในเวลาเดียวกันและทำงานพร้อมกันในพื้นหลัง asyncซึ่งสามารถทำได้ด้วย

asyncส่งคืนอินสแตนซ์ของDeffered<T>ประเภทโดยที่Tเป็นประเภทข้อมูลที่ฟังก์ชันระงับของเราส่งคืน ตัวอย่างเช่น,

  • downloadTask1()จะกลับมาDeferred<String>เป็นสตริงเป็นประเภทส่งคืนของฟังก์ชั่น
  • downloadTask2()จะส่งคืนDeferred<Int>เนื่องจาก Int เป็นฟังก์ชันส่งคืนประเภท
  • downloadTask3()จะส่งคืนDeferred<Float>เนื่องจากฟังก์ชัน Float เป็นประเภทส่งคืน

เราสามารถใช้อ็อบเจกต์ return จากasyncชนิดDeferred<T>เพื่อรับค่าส่งคืนเป็นTชนิด ที่สามารถทำได้ด้วยการawait()โทร ตรวจสอบโค้ดด้านล่าง

        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
        }

ด้วยวิธีนี้เราได้เปิดตัวทั้ง 3 งานพร้อมกัน ดังนั้นเวลาดำเนินการทั้งหมดของฉันในการดำเนินการจึงเป็นเพียง8 secondsช่วงเวลาdownloadTask2()เดียวเนื่องจากเป็นงานที่ใหญ่ที่สุดของทั้ง 3 งาน คุณสามารถเห็นสิ่งนี้ได้ในสกรีนช็อตต่อไปนี้ในToast message

รอตัวอย่าง


1
ขอขอบคุณสำหรับการกล่าวขวัญว่าlaunchเป็นลำดับ funs ในขณะที่asyncสำหรับพร้อมกัน
Akbolat SSS

คุณใช้การเปิดตัวหนึ่งครั้งสำหรับงานทั้งหมดและ async สำหรับแต่ละงาน อาจเร็วกว่าเพราะแต่ละอันถูกเปิดตัวใน coroutine อื่นและไม่รอใครเลย นี่เป็นการเปรียบเทียบที่ไม่ถูกต้อง ประสิทธิภาพมักจะเหมือนกัน ความแตกต่างที่สำคัญอย่างหนึ่งคือการเปิดตัวเริ่มต้น coroutine ใหม่แทนที่จะเป็น async ที่แยกเจ้าของออกมา อีกหนึ่งปัจจัยคือถ้าหนึ่งในงาน async จะล้มเหลวด้วยเหตุผล coroutine หลักจะล้มเหลวอย่างใดอย่างหนึ่ง นั่นเป็นสาเหตุที่ทำให้ async ไม่เป็นที่นิยมเท่าการเปิด
p2lem8dev

1
คำตอบนี้ไม่ถูกต้องเปรียบเทียบ async กับฟังก์ชั่นระงับโดยตรงแทนที่จะเปิดตัว แทนที่จะเรียกฟังก์ชั่น suspend โดยตรงตัวอย่างเช่นถ้าคุณเรียกใช้งาน (Dispatchers.IO) {downloadTask1 ()} คุณจะเห็นว่าทั้งคู่ทำงานพร้อมกันไม่ใช่ตามลำดับคุณจะไม่สามารถรับเอาต์พุตได้ แต่คุณจะเห็นว่ามันเป็น ไม่ต่อเนื่อง นอกจากนี้หากคุณไม่ต่อเชื่อม deferred.await () และโทร deferred.await () แยกกันคุณจะเห็นว่า async นั้นเรียงตามลำดับ
ธราเซียน

2
-1 นี่มันผิดธรรมดา ทั้งสองlaunchและasyncจะเริ่มต้น coroutines ใหม่ คุณกำลังเปรียบเทียบ coroutine เดียวกับไม่มีลูกกับ coroutine เดียวกับ 3 ลูก คุณสามารถแทนที่การasyncเรียกใช้แต่ละรายการด้วยlaunchและไม่มีอะไรเปลี่ยนแปลงได้อย่างแน่นอนเกี่ยวกับการเกิดพร้อมกัน
Moira

เสียงที่ไม่เกี่ยวข้องในคำตอบนี้คือการเพิ่มความซับซ้อนที่อยู่นอกหัวข้อร่วมประจำ
trueadjustr

6
  1. ทั้งผู้สร้าง coroutine คือ launch และ async นั้นเป็น lambdas โดยมีตัวรับชนิด CoroutineScope ซึ่งหมายความว่า Inner block ของพวกเขานั้นถูกคอมไพล์เป็นฟังก์ชั่น suspend ดังนั้นพวกเขาทั้งสองจึงทำงานในโหมดอะซิงโครนัส

  2. ความแตกต่างระหว่างการเปิดตัวและ async คือการเปิดใช้งานความเป็นไปได้ที่แตกต่างกันสองแบบ ตัวสร้างการเปิดใช้จะส่งคืนงานอย่างไรก็ตามฟังก์ชัน async จะส่งคืนวัตถุที่ถูกเลื่อน คุณสามารถใช้การเรียกใช้เพื่อเรียกใช้บล็อกที่คุณไม่คาดหวังว่าจะได้รับค่าที่ส่งคืนจากมันเช่นการเขียนไปยังฐานข้อมูลหรือการบันทึกไฟล์ ในทางกลับกัน async ซึ่งคืนค่า Deferred ตามที่ฉันระบุไว้ก่อนหน้านี้จะส่งคืนค่าที่มีประโยชน์จากการดำเนินการของบล็อกวัตถุที่ล้อมข้อมูลของคุณดังนั้นคุณสามารถใช้มันเพื่อผลลัพธ์ส่วนใหญ่ แต่อาจมีผลข้างเคียงเช่นกัน หมายเหตุ: คุณสามารถดึงแถบรอการตัดบัญชีและรับค่าโดยใช้ฟังก์ชันรอซึ่งจะปิดกั้นการดำเนินการของคำสั่งของคุณจนกว่าจะส่งคืนค่าหรือโยนข้อยกเว้น!

  3. ทั้งตัวสร้าง coroutine (เรียกใช้และ async) สามารถยกเลิกได้

  4. มีอะไรอีกไหม?: อ๋อด้วยการปล่อยถ้ามีข้อผิดพลาดเกิดขึ้นในบล็อกของมัน coroutine จะถูกยกเลิกโดยอัตโนมัติและส่งข้อยกเว้น ในทางกลับกันหากเกิดขึ้นกับ async ข้อยกเว้นจะไม่ถูกเผยแพร่เพิ่มเติมและควรถูกจับ / จัดการภายในวัตถุ Deferred ที่ส่งคืน

  5. เพิ่มเติมเกี่ยวกับ coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1


1
ขอบคุณสำหรับความคิดเห็นนี้ มันรวบรวมคะแนนทั้งหมดของกระทู้ ฉันจะเพิ่มว่าการเปิดตัวบางรายการไม่ได้ถูกยกเลิกเช่น Atomic ไม่สามารถยกเลิกได้
p2lem8dev

4

การเปิดตัวส่งคืนงาน

asyncส่งคืนผลลัพธ์ (งานที่ถูกเลื่อนออกไป)

เปิดตัวด้วยการเข้าร่วมจะใช้ในการรอจนกว่างานจะเสร็จสิ้นเพียงแค่หยุดการเรียก coroutine เรียกเข้าร่วม () ปล่อยกระทู้ปัจจุบันฟรีที่จะทำงานอื่น ๆ (เช่นการดำเนินการ coroutine อื่น) ในระหว่างนี้

asyncใช้ในการคำนวณผลลัพธ์บางอย่าง มันสร้าง coroutine และส่งกลับผลลัพธ์ในอนาคตเป็นการดำเนินการตาม Deferred coroutine ที่ทำงานอยู่จะถูกยกเลิกเมื่อผลลัพธ์ที่เลื่อนออกไปถูกยกเลิก

พิจารณาเมธอด async ที่ส่งคืนค่าสตริง หากวิธีการ async ถูกนำมาใช้โดยไม่ต้องรอมันจะส่งกลับสตริงที่รอการตัดบัญชี แต่ถ้าใช้การรอคุณจะได้รับสตริงตามผลลัพธ์

ความแตกต่างที่สำคัญระหว่าง async และการเปิดตัว การรอการตัดบัญชีส่งคืนค่าเฉพาะประเภท T หลังจาก Coroutine ของคุณเสร็จสิ้นการดำเนินการในขณะที่งานไม่ได้


0

Async vs Launch Async vs Launch Diff Image

เปิดตัว / async ไม่มีผลลัพธ์

  • ใช้เมื่อไม่ต้องการผลลัพธ์
  • อย่าปิดกั้นรหัสที่เรียกว่า
  • วิ่งคู่ขนาน

async สำหรับผลลัพธ์

  • เมื่อคุณต้องรอผลลัพธ์และสามารถทำงานแบบขนานเพื่อประสิทธิภาพ
  • บล็อกรหัสที่เรียกว่า
  • วิ่งขนาน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.