ในkotlinx.coroutinesไลบรารีคุณสามารถเริ่ม coroutine ใหม่โดยใช้launch(กับjoin) หรือasync(พร้อมawait) ความแตกต่างระหว่างพวกเขาคืออะไร?
ในkotlinx.coroutinesไลบรารีคุณสามารถเริ่ม coroutine ใหม่โดยใช้launch(กับjoin) หรือasync(พร้อมawait) ความแตกต่างระหว่างพวกเขาคืออะไร?
คำตอบ:
launchจะใช้ในการยิงและลืม coroutine มันก็เหมือนกับการเริ่มหัวข้อใหม่ หากโค้ดภายในการlaunchยุติมีข้อยกเว้นจะถือว่าเป็นข้อยกเว้นที่ไม่ถูกตรวจจับในเธรดโดยปกติจะพิมพ์ไปที่ stderr ในแอปพลิเคชัน JVM แบ็กเอนด์และแอปพลิเคชัน Android ขัดข้อง joinถูกใช้เพื่อรอให้ coroutine ที่เปิดตัวเสร็จสมบูรณ์และไม่เผยแพร่ข้อยกเว้น อย่างไรก็ตามcoroutine เด็กที่ล้มเหลวจะยกเลิกพาเรนต์ด้วยข้อยกเว้นที่เกี่ยวข้องเช่นกัน
asyncจะใช้ในการเริ่มต้น coroutine ที่คำนวณผลบางอย่าง ผลลัพธ์แสดงโดยอินสแตนซ์ของDeferredและคุณต้องใช้awaitมัน ข้อยกเว้นที่ไม่ได้ตรวจสอบภายในasyncรหัสจะถูกเก็บไว้ในผลลัพธ์Deferredและจะไม่ถูกส่งไปที่อื่นมันจะถูกลบอย่างเงียบ ๆ ยกเว้นว่ามีการประมวลผล คุณต้องไม่ลืมเกี่ยวกับ coroutine ที่คุณได้เริ่มต้นด้วยการ async
ฉันพบคู่มือนี้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 ก็ต่อเมื่อต้องการผลโดยบางคนรอหรือถ้าฟังก์ชั่นเริ่มต้นจะถูกเรียกใช้
launchและasyncถูกใช้เพื่อเริ่มต้น coroutines ใหม่ แต่พวกเขาดำเนินการในลักษณะที่แตกต่างกัน
ฉันต้องการแสดงตัวอย่างพื้นฐานที่จะช่วยให้คุณเข้าใจความแตกต่างได้ง่ายมาก
- เปิด
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
- 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
launchเป็นลำดับ funs ในขณะที่asyncสำหรับพร้อมกัน
launchและasyncจะเริ่มต้น coroutines ใหม่ คุณกำลังเปรียบเทียบ coroutine เดียวกับไม่มีลูกกับ coroutine เดียวกับ 3 ลูก คุณสามารถแทนที่การasyncเรียกใช้แต่ละรายการด้วยlaunchและไม่มีอะไรเปลี่ยนแปลงได้อย่างแน่นอนเกี่ยวกับการเกิดพร้อมกัน
ทั้งผู้สร้าง coroutine คือ launch และ async นั้นเป็น lambdas โดยมีตัวรับชนิด CoroutineScope ซึ่งหมายความว่า Inner block ของพวกเขานั้นถูกคอมไพล์เป็นฟังก์ชั่น suspend ดังนั้นพวกเขาทั้งสองจึงทำงานในโหมดอะซิงโครนัส
ความแตกต่างระหว่างการเปิดตัวและ async คือการเปิดใช้งานความเป็นไปได้ที่แตกต่างกันสองแบบ ตัวสร้างการเปิดใช้จะส่งคืนงานอย่างไรก็ตามฟังก์ชัน async จะส่งคืนวัตถุที่ถูกเลื่อน คุณสามารถใช้การเรียกใช้เพื่อเรียกใช้บล็อกที่คุณไม่คาดหวังว่าจะได้รับค่าที่ส่งคืนจากมันเช่นการเขียนไปยังฐานข้อมูลหรือการบันทึกไฟล์ ในทางกลับกัน async ซึ่งคืนค่า Deferred ตามที่ฉันระบุไว้ก่อนหน้านี้จะส่งคืนค่าที่มีประโยชน์จากการดำเนินการของบล็อกวัตถุที่ล้อมข้อมูลของคุณดังนั้นคุณสามารถใช้มันเพื่อผลลัพธ์ส่วนใหญ่ แต่อาจมีผลข้างเคียงเช่นกัน หมายเหตุ: คุณสามารถดึงแถบรอการตัดบัญชีและรับค่าโดยใช้ฟังก์ชันรอซึ่งจะปิดกั้นการดำเนินการของคำสั่งของคุณจนกว่าจะส่งคืนค่าหรือโยนข้อยกเว้น!
ทั้งตัวสร้าง coroutine (เรียกใช้และ async) สามารถยกเลิกได้
มีอะไรอีกไหม?: อ๋อด้วยการปล่อยถ้ามีข้อผิดพลาดเกิดขึ้นในบล็อกของมัน coroutine จะถูกยกเลิกโดยอัตโนมัติและส่งข้อยกเว้น ในทางกลับกันหากเกิดขึ้นกับ async ข้อยกเว้นจะไม่ถูกเผยแพร่เพิ่มเติมและควรถูกจับ / จัดการภายในวัตถุ Deferred ที่ส่งคืน
เพิ่มเติมเกี่ยวกับ coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1
การเปิดตัวส่งคืนงาน
asyncส่งคืนผลลัพธ์ (งานที่ถูกเลื่อนออกไป)
เปิดตัวด้วยการเข้าร่วมจะใช้ในการรอจนกว่างานจะเสร็จสิ้นเพียงแค่หยุดการเรียก coroutine เรียกเข้าร่วม () ปล่อยกระทู้ปัจจุบันฟรีที่จะทำงานอื่น ๆ (เช่นการดำเนินการ coroutine อื่น) ในระหว่างนี้
asyncใช้ในการคำนวณผลลัพธ์บางอย่าง มันสร้าง coroutine และส่งกลับผลลัพธ์ในอนาคตเป็นการดำเนินการตาม Deferred coroutine ที่ทำงานอยู่จะถูกยกเลิกเมื่อผลลัพธ์ที่เลื่อนออกไปถูกยกเลิก
พิจารณาเมธอด async ที่ส่งคืนค่าสตริง หากวิธีการ async ถูกนำมาใช้โดยไม่ต้องรอมันจะส่งกลับสตริงที่รอการตัดบัญชี แต่ถ้าใช้การรอคุณจะได้รับสตริงตามผลลัพธ์
ความแตกต่างที่สำคัญระหว่าง async และการเปิดตัว การรอการตัดบัญชีส่งคืนค่าเฉพาะประเภท T หลังจาก Coroutine ของคุณเสร็จสิ้นการดำเนินการในขณะที่งานไม่ได้
Async vs Launch Async vs Launch Diff Image
เปิดตัว / async ไม่มีผลลัพธ์
async สำหรับผลลัพธ์