ใน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 - อนาคตที่ไม่ปิดกั้นน้ำหนักเบาซึ่งแสดงถึงคำมั่นสัญญาที่จะให้ผลลัพธ์ในภายหลัง
ดังนั้นเริ่มด้ายพื้นหลังไม่บางสิ่งบางอย่างและส่งกลับโทเค็นทันทีasync
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")
}
คุณสามารถใช้. 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 สำหรับผลลัพธ์