async — คือ Coroutine Builder ใน Kotlin ที่เริ่มแล่น coroutine และส่งคืนอ็อบเจ็กต์ Deferred<T> เพื่อรับผลลัพธ์ของการทำงานแบบไม่ชิงครอบครัง async ช่วยให้สามารถทำงานหลายอย่างแบบขนานานและรวมผลลัพธ์ผ่าน await() ตาม คู่มือ JetBrains, 2024 async เป็นวิธีที่ต้องการในการจัดระเบียบการคำนวณแบบขนานานใน coroutine
หัวข้อสำคัญ
async — เป็นฟังก์ชันส่วนขยายของ CoroutineScope ที่สร้าง coroutine และส่งคืนผลลัพธ์ ต่างจาก launch, async ส่งคืน Deferred<T> — อ็อบเจ็กต์ที่แทนค่าในอนาคต Coroutine ที่เริ่มผ่าน async ทำงานพร้อมกับ coroutine อื่น ช่วยให้สามารถทำงานคำนวณแบบขนานานได้
async ถูกใช้เมื่อต้องการผลลัพธ์ของการทำงานแบบไม่ชิงครอบครังสำหรับการประมวลผลต่อ: โหลดข้อมูลจากหลายแหล่ง, การประมวลผลทีละชุดของคอแลกชัน, การร้องขอแบบขนานานไปยัง API ต่าง ๆ Deferred ถูกสร้างทันที และผลลัพธ์จะพร้อมใช้งานหลังจาก coroutine สิ้นสุด
import kotlinx.coroutines.*
suspend fun String.delayedValue(): String {
delay(1000L)
return this
}
fun main() = runBlocking {
val deferred: Deferred<String> = async { "Hello".delayedValue() }
println(deferred.await())
}
ลายเซ็นของ async คล้ายกับ launch: พารามิเตอร์ CoroutineContext, CoroutineStart เหมือนกัน และบล็อก suspend ความแตกต่างอยู่ที่ชนิดของค่าที่ส่งคืน — Deferred<T> แทน Job, โดย T คือชนิดของผลลัพธ์ที่ส่งคืนโดยนิพจน์สุดท้ายของบล็อก
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
Deferred<T> — อินเตอร์เฟสที่สืบทอด Job และเพิ่มเมธอด await(), getCompleted(), getCompletionExceptionOrNull() await() เป็นฟังก์ชัน suspend และสามารถเรียกได้ภายใน coroutine หรือฟังก์ชัน suspend อื่น getCompleted() เป็นเมธอดที่ไม่บล็อก ซึ่งจะเกิดข้อยกเว้นหาก coroutine ยังไม่สิ้นสุด
ข้อดีหลักของ async คือความสามารถในการทำงานแบบขนานาน แทนที่จะรอแต่ละการทำงานเรียงลำดับ (เวลารวม = ผลรวมของเวลา), async เริ่ม coroutine พร้อมกัน ทำให้เวลารวมลดลงเป็นเวลาของ การทำงานที่ยาวที่สุด
สถานการณ์ทั่วไป — โหลดโปรไฟล์ผู้ใช้, การตั้งค่า และประวัติคำสั่งซื้อด้วยคำขอ async แบบขนานานสามรายการ หลังจากทั้งสามสิ้นสุด ผลลัพธ์จะถูกรวมเข้าด้วยกันเป็นโมเดลข้อมูลเดียว
suspend fun loadUserDashboard(userId: String): Dashboard = coroutineScope {
val profile = async { api.getProfile(userId) }
val settings = async { api.getSettings(userId) }
val orders = async { api.getRecentOrders(userId) }
Dashboard(
profile = profile.await(),
settings = settings.await(),
orders = orders.await()
)
}
ฟังก์ชัน coroutineScope เริ่ม coroutine ย่อย และรอให้มันสิ้นสุดก่อนจะส่งคืนผลลัพธ์ บล็อกทั้งหมดทำงานแบบขนานาน, ไม่ใช่เรียงลำดับ
Deferred รองรับหลายเมธอดในการทำงานกับผลลัพธ์ นอกเหนือจาก await() แล้ว ยังมีเมธอดสำหรับตรวจสอบสถานะ, รับค่าที่สิ้นสุด และรวมหลาย Deferred ผ่านฟังก์ชันอำนวยความสะดวกของ Kotlin Coroutines
| เมธอด | คำอธิบาย | Suspend? |
|---|---|---|
| await() | หยุด coroutine ชั่วคราวจนกว่าจะได้รับผลลัพธ์ | ใช่ |
| getCompleted() | ส่งคืนผลลัพธ์หาก coroutine สิ้นสุดแล้ว (มิฉะนั้น Exception) | ไม่ |
| getCompletionExceptionOrNull() | ส่งคืนข้อยกเว้นเมื่อเกิดข้อผิดพลาดหรือ null | ไม่ |
| isCompleted | ตรวจสอบว่า coroutine สิ้นสุดแล้วหรือไม่ | ไม่ |
สำหรับการรวมรายการ Deferred awaitAll() ถูกใช้ — ฟังก์ชันที่หยุดการทำงานไว้ชั่วคราวจนกว่า Deferred ทั้งหมดในคอแลกชันจะสิ้นสุด หากอย่างน้อยหนึ่งรายการสิ้นสุดด้วยข้อผิดพลาด awaitAll() จะเกิดข้อยกเว้น
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
การเลือกระหว่าง async และ launch ขึ้นอยู่กับว่าต้องการผลลัพธ์ของ coroutine หรือไม่ launch ส่งคืน Job และเหมาะสำหรับงาน fire-and-forget async ส่งคืน Deferred และใช้สำหรับงานที่ส่งคืนข้อมูล ในแง่ของไวยาการณ์และพารามิเตอร์ ตัวสร้างทั้งสองเหมือนกัน
กฎที่สำคัญ: อย่าใช้ async สำหรับ fire-and-forget หากไม่ต้องการผลลัพธ์ของ coroutine ให้ใช้ launch async สร้างโอเวอร์เฮดของการสร้าง Deferred ซึ่งไม่สมเหตุสมผลในสถานการณ์ดังกล่าว
ใน Android, async ถูกใช้สำหรับการดำเนินการแบบขนานานภายใน ViewModel และ UseCases สถานการณ์ทั่วไป: โหลดโปรไฟล์และฟีดข่ଲวพร้อมกัน, รับข้อมูลสภาพอากาศจากหลายบริการสภาพอากาศ, การซิงโครไนซ์ข้อมูลแบบแบตช์กับเซิร์ฟเวอร์
class HomeViewModel : ViewModel() {
fun loadHomeScreen() {
viewModelScope.launch {
val userData = async(Dispatchers.IO) { repository.getUserData() }
val newsFeed = async(Dispatchers.IO) { repository.getNewsFeed() }
val notifications = async(Dispatchers.IO) { repository.getNotifications() }
val state = HomeState(
user = userData.await(),
news = newsFeed.await(),
unread = notifications.await()
)
_uiState.update { state }
}
}
}
แนะนำให้ห่อกลุ่ม async ใน coroutineScope หรือ supervisorScope เพื่อแยกข้อผิดพลาด supervisorScope ช่วยให้ coroutine async อื่นสามารถทำงานต่อไปได้แม้ว่าหนึ่งจะล้มเหลว — มีประโยชน์สำหรับงานที่ไม่สำคัญ
คำถามที่พบบ่อย
ได้ แต่ไม่มีความหมาย async โดยไม่มี await() เริ่ม coroutine แต่ผลลัพธ์ของมันจะหายไป หากไม่ต้องการผลลัพธ์ ให้ใช้ launch — มันไม่สร้าง Deferred และมีประสิทธิภาพด้านหน่วยความจำและประสิทธิภาพมากกว่า
ข้อยกเว้นภายใน async จะถูกเก็บไว้ใน Deferred การเรียก await() จะเกิดข้อยกเว้นนี้ หากต้องการตรวจสอบโดยไม่เกิดข้อยกเว้น ให้ใช้ getCompletionExceptionOrNull() เมื่อใช้ coroutineScope ข้อผิดพลาดจะยกเลิก coroutine ย่อยทั้งหมด
awaitAll() — ฟังก์ชันส่วนขยายสำหรับ Iterable<Deferred<T>> ที่หยุด coroutine ชั่วคราวจนกว่า Deferred ทั้งหมดจะสิ้นสุด ส่งคืนรายการผลลัพธ์ หากอย่างน้อยหนึ่ง Deferred ล้มเหลว มันจะเกิดข้อยกเว้นและยกเลิกส่วนที่เหลือ
ใช่ async เป็นฟังก์ชันส่วนขยายของ CoroutineScope ที่ผูกกับขอบเขต GlobalScope.async สร้าง coroutine โดยไม่ผูกกับวงจรของชีวิต — มันสามารถทำงานได้อย่างไม่จำกัด GlobalScope ไม่ได้รับคำแนะนำใน Android เนื่องจากความเสี่ยงของการรั่วไหล
จำนวนจะถูกกำหนดโดยตัวจัดการ: Dispatchers.IO รองรับได้สูงสุด 64 เธรดพร้อมกัน, Dispatchers.Default — สูงสุดตามจำนวนแกน CPU สำหรับงานจำนวนมาก ให้ใช้การทำงานแบบขนานานแบบจำกัดผ่าน Semaphore หรือ mapNotNull กับ awaitAll
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม