API ที่ไม่บล็อกจะส่งคําขอทํางาน จากนั้นส่งการควบคุมกลับไปยังเธรดเรียกใช้เพื่อให้ทํางานอื่นๆ ได้ก่อนที่การดำเนินการที่ขอจะเสร็จสมบูรณ์ API เหล่านี้มีประโยชน์ในกรณีที่งานที่ได้รับคําขออาจดําเนินการอยู่หรืออาจต้องรอให้ I/O หรือ IPC เสร็จสมบูรณ์ ความพร้อมใช้งานของทรัพยากรระบบที่มีการแข่งขันสูง หรืออินพุตของผู้ใช้ก่อนจึงจะดําเนินการต่อได้ API ที่ออกแบบมาอย่างดีจะมีวิธียกเลิกการดำเนินการที่กำลังดำเนินอยู่และหยุดการดำเนินการในนามของผู้เรียกให้ดำเนินการครั้งแรก ซึ่งจะช่วยรักษาประสิทธิภาพของระบบและอายุการใช้งานแบตเตอรี่เมื่อไม่จำเป็นต้องดำเนินการอีกต่อไป
API แบบไม่พร้อมกันเป็นวิธีหนึ่งในการทำให้การดำเนินการเป็นแบบไม่บล็อก Async API จะยอมรับการดําเนินการต่อหรือคอลแบ็กบางรูปแบบซึ่งจะได้รับการแจ้งเตือนเมื่อการดำเนินการเสร็จสมบูรณ์ หรือเมื่อเกิดเหตุการณ์อื่นๆ ในระหว่างการดำเนินการ
แรงจูงใจหลัก 2 ข้อในการเขียน API แบบไม่สอดคล้องกันมีดังนี้
- การดำเนินการหลายรายการพร้อมกัน โดยต้องเริ่มการดำเนินการที่ n ก่อนที่การดำเนินการที่ n-1 จะเสร็จสมบูรณ์
- หลีกเลี่ยงการบล็อกเธรดการเรียกใช้จนกว่าการดำเนินการจะเสร็จสมบูรณ์
Kotlin สนับสนุนการทำงานพร้อมกันแบบมีโครงสร้างอย่างจริงจัง ซึ่งเป็นชุดหลักการและ API ที่สร้างขึ้นจากฟังก์ชันการระงับที่แยกการเรียกใช้โค้ดแบบซิงค์และแบบแอซิงค์ออกจากลักษณะการบล็อกเธรด ฟังก์ชันการระงับจะไม่บล็อกและทำงานพร้อมกัน
ระงับฟังก์ชัน
- อย่าบล็อกเธรดการเรียก ให้ใช้เธรดการดำเนินการเป็นรายละเอียดการติดตั้งใช้งานขณะรอผลลัพธ์ของการดำเนินการที่ทำงานอยู่จากที่อื่น
- ดำเนินการแบบซิงค์และไม่ต้องให้ผู้เรียก API แบบไม่บล็อกดำเนินการต่อไปพร้อมกันกับงานที่ไม่มีการบล็อกซึ่งเริ่มต้นโดยคําเรียก API
หน้านี้แสดงรายละเอียดพื้นฐานขั้นต่ำของสิ่งที่นักพัฒนาแอปคาดหวังได้เมื่อทำงานกับ API แบบไม่บล็อกและแบบแอซิงโครนัส ตามด้วยชุดสูตรสำหรับการเขียน API ที่ตรงกับสิ่งที่นักพัฒนาแอปคาดหวังเหล่านี้ในภาษา Kotlin หรือ Java ในแพลตฟอร์ม Android หรือไลบรารี Jetpack หากไม่แน่ใจ ให้พิจารณาความคาดหวังของนักพัฒนาแอปเป็นข้อกำหนดสำหรับแพลตฟอร์ม API ใหม่
สิ่งที่นักพัฒนาแอปคาดหวังจาก Async API
ความคาดหวังต่อไปนี้เขียนขึ้นจากมุมมองของ API ที่ไม่ระงับ เว้นแต่จะระบุไว้เป็นอย่างอื่น
API ที่ยอมรับการติดต่อกลับมักจะเป็นแบบไม่พร้อมกัน
หาก API ยอมรับการเรียกกลับที่ไม่ได้ระบุไว้ในเอกสารว่ามีการเรียกใช้ในตำแหน่งเท่านั้น (กล่าวคือ เรียกใช้โดยเธรดเรียกใช้เท่านั้นก่อนที่การเรียก API เองจะแสดงผล) ระบบจะถือว่า API เป็นแบบแอซิงโครไนซ์ และ API ดังกล่าวควรเป็นไปตามความคาดหวังอื่นๆ ทั้งหมดที่ระบุไว้ในส่วนต่อไปนี้
ตัวอย่างของ Callback ที่เรียกใช้เฉพาะในตำแหน่งคือฟังก์ชันการแมปหรือตัวกรองระดับสูงที่เรียกใช้ตัวแมปหรือนิพจน์เชิงตรรกะกับแต่ละรายการในคอลเล็กชันก่อนที่จะแสดงผล
API แบบไม่พร้อมกันควรแสดงผลโดยเร็วที่สุด
นักพัฒนาแอปคาดหวังว่า API แบบแอซิงค์จะไม่บล็อกและแสดงผลอย่างรวดเร็วหลังจากเริ่มคําขอดําเนินการ การเรียก Async API ควรปลอดภัยเสมอไม่ว่าจะเรียกเมื่อใดก็ตาม และการเรียก Async API ไม่ควรส่งผลให้เฟรมกระตุกหรือ ANR
แพลตฟอร์มหรือไลบรารีสามารถเรียกใช้การดำเนินการและสัญญาณเกี่ยวกับวงจรชีวิตของโค้ดได้แบบออนดีมานด์ และคาดหวังให้นักพัฒนาแอปมีความรู้ทั่วโลกเกี่ยวกับเว็บไซต์ที่อาจเรียกใช้โค้ดของตนทั้งหมดนั้นไม่ยั่งยืน ตัวอย่างเช่น Fragment
อาจเพิ่มลงใน FragmentManager
ในธุรกรรมแบบซิงค์เพื่อตอบสนองต่อการวัดและเลย์เอาต์ View
เมื่อต้องป้อนข้อมูลเนื้อหาแอปเพื่อเติมเต็มพื้นที่ว่าง (เช่น RecyclerView
) LifecycleObserver
ที่ตอบสนองต่อการเรียกกลับวงจรชีวิตของ onStart
ของข้อมูลโค้ดนี้อาจดำเนินการเริ่มต้นแบบครั้งเดียวที่นี่ ซึ่งอาจอยู่ในเส้นทางโค้ดที่สำคัญสำหรับการสร้างเฟรมภาพเคลื่อนไหวที่ราบรื่น นักพัฒนาแอปควรมั่นใจเสมอว่าการเรียกใช้ any async API เพื่อตอบสนองต่อการเรียกกลับเกี่ยวกับวงจรชีวิตของประเภทเหล่านี้จะไม่เป็นสาเหตุของเฟรมที่กระตุก
ซึ่งหมายความว่างานที่ API แบบแอสซิงค์ทําก่อนแสดงผลต้องเบามาก เช่น สร้างบันทึกคําขอและ Callback ที่เชื่อมโยง และลงทะเบียนกับเครื่องมือการดําเนินการที่ทํางานมากที่สุด หากการลงทะเบียนสําหรับการดําเนินการแบบแอซิงค์ต้องใช้ IPC การใช้งาน API ควรใช้มาตรการที่จําเป็นเพื่อตอบสนองความคาดหวังของนักพัฒนาแอป ซึ่งอาจรวมถึงรายการต่อไปนี้อย่างน้อย 1 รายการ
- การใช้ IPC พื้นฐานเป็นการเรียก Binder แบบ 1 ทิศทาง
- การเรียก Binder แบบ 2 ทางไปยังเซิร์ฟเวอร์ระบบที่การลงทะเบียนเสร็จสมบูรณ์ไม่จำเป็นต้องมีการล็อกที่มีการแย่งกันใช้สูง
- การส่งคําขอไปยังเธรดผู้ทํางานในกระบวนการของแอปเพื่อดำเนินการลงทะเบียนการบล็อกผ่าน IPC
API แบบไม่สอดคล้องกันควรแสดงผลเป็นค่าว่างและแสดงข้อยกเว้นเฉพาะในกรณีที่อาร์กิวเมนต์ไม่ถูกต้อง
API แบบแอซิงค์ควรรายงานผลลัพธ์ทั้งหมดของการดำเนินการที่ขอไปยังการเรียกกลับที่ระบุ ซึ่งช่วยให้นักพัฒนาซอฟต์แวร์ใช้เส้นทางโค้ดเดียวสําหรับการจัดการข้อผิดพลาดและความสําเร็จได้
API แบบแอซิงค์อาจตรวจสอบอาร์กิวเมนต์ว่ามีค่าเป็น Null และแสดงข้อผิดพลาด NullPointerException
หรือตรวจสอบว่าอาร์กิวเมนต์ที่ระบุอยู่ในช่วงที่ใช้ได้และแสดงข้อผิดพลาด IllegalArgumentException
เช่น สําหรับฟังก์ชันที่ยอมรับ float
ที่อยู่ระหว่าง 0
ถึง 1f
ฟังก์ชันอาจตรวจสอบว่าพารามิเตอร์อยู่ในช่วงนี้หรือไม่ และแสดงข้อผิดพลาด IllegalArgumentException
หากอยู่นอกช่วง หรืออาจตรวจสอบ String
แบบสั้นว่าเป็นไปตามรูปแบบที่ถูกต้องหรือไม่ เช่น ใช้อักขระที่เป็นตัวอักษรและตัวเลขเท่านั้น (โปรดทราบว่าเซิร์ฟเวอร์ระบบไม่ควรเชื่อถือกระบวนการของแอป บริการของระบบควรทำการตรวจสอบเหล่านี้ซ้ำในบริการของระบบเอง)
ควรรายงานข้อผิดพลาดอื่นๆ ทั้งหมดไปยังการเรียกกลับที่ระบุ ซึ่งรวมถึงแต่ไม่จำกัดเพียงรายการต่อไปนี้
- ความล้มเหลวของการดำเนินการที่ขอที่เกิดขึ้นที่เทอร์มินัล
- ข้อยกเว้นด้านความปลอดภัยสำหรับการให้สิทธิ์หรือสิทธิ์ที่จำเป็นซึ่งขาดหายไปเพื่อดำเนินการให้เสร็จสมบูรณ์
- ใช้โควต้าเกินขีดจํากัดสําหรับการดําเนินการ
- กระบวนการของแอปไม่อยู่ใน "เบื้องหน้า" มากพอที่จะดําเนินการ
- ยกเลิกการเชื่อมต่อฮาร์ดแวร์ที่จำเป็นแล้ว
- เครือข่ายล้มเหลว
- หมดเวลา
- Binder สิ้นสุดลงหรือกระบวนการระยะไกลไม่พร้อมใช้งาน
API แบบไม่ซิงค์ควรมีกลไกการยกเลิก
Async API ควรมีวิธีระบุการดำเนินการที่ดำเนินอยู่ว่าผู้เรียกไม่สนใจผลลัพธ์อีกต่อไป การดำเนินการยกเลิกนี้ควรส่งสัญญาณ 2 อย่างต่อไปนี้
ควรปล่อยการอ้างอิงแบบฮาร์ดไปยังการเรียกกลับที่ผู้เรียกระบุ
แบ็กคอลที่ระบุให้กับ API แบบแอ็กซิงโครนัสอาจมีการอ้างอิงแบบฮาร์ดไปยังกราฟออบเจ็กต์ขนาดใหญ่ และงานที่กำลังดำเนินอยู่ซึ่งมีการอ้างอิงแบบฮาร์ดไปยังแบ็กคอลนั้นอาจทำให้ระบบไม่เก็บขยะกราฟออบเจ็กต์เหล่านั้น การปล่อยการอ้างอิงการเรียกกลับเหล่านี้เมื่อยกเลิกจะทำให้กราฟออบเจ็กต์เหล่านี้มีสิทธิ์รับการเก็บขยะเร็วกว่ามากเมื่อเทียบกับกรณีที่อนุญาตให้งานทำงานจนเสร็จสมบูรณ์
เครื่องมือการดําเนินการทํางานสําหรับผู้เรียกอาจหยุดการทํางานนั้น
งานเริ่มต้นโดยการเรียก API แบบแอซิงค์อาจใช้พลังงานหรือทรัพยากรระบบอื่นๆ เป็นจำนวนมาก API ที่อนุญาตให้ผู้เรียกใช้ส่งสัญญาณเมื่อไม่จำเป็นต้องทํางานนี้อีกต่อไปจะอนุญาตให้หยุดการทํางานนั้นก่อนที่จะใช้ทรัพยากรของระบบเพิ่มเติม
ข้อควรพิจารณาพิเศษสำหรับแอปที่แคชไว้หรือหยุดทำงาน
เมื่อออกแบบ API แบบไม่พร้อมกันซึ่งการเรียกกลับมาจากกระบวนการของระบบและส่งไปยังแอป ให้พิจารณาสิ่งต่อไปนี้
- กระบวนการและวงจรชีวิตของแอป: กระบวนการของแอปผู้รับอาจอยู่ในสถานะแคชไว้
- การแช่แข็งแอปที่แคชไว้: กระบวนการของแอปผู้รับอาจหยุดทำงาน
เมื่อกระบวนการของแอปเข้าสู่สถานะแคชไว้ หมายความว่ากระบวนการดังกล่าวไม่ได้โฮสต์คอมโพเนนต์ที่ผู้ใช้มองเห็น เช่น กิจกรรมและบริการ ระบบจะเก็บแอปไว้ในหน่วยความจำเผื่อว่าผู้ใช้จะเห็นแอปอีกครั้ง แต่ในระหว่างนี้แอปไม่ควรทำงาน ในกรณีส่วนใหญ่ คุณควรหยุดการส่ง App callback ชั่วคราวเมื่อแอปนั้นเข้าสู่สถานะแคชไว้ แล้วกลับมาดำเนินการต่อเมื่อแอปออกจากสถานะแคชไว้ เพื่อไม่ให้เกิดการทํางานในกระบวนการของแอปที่แคชไว้
แอปที่แคชไว้อาจค้างด้วย เมื่อแอปค้างอยู่ แอปจะได้รับเวลา CPU เท่ากับ 0 และไม่สามารถทำงานใดๆ ได้เลย การเรียกใช้แคลบแบ็กที่ลงทะเบียนของแอปนั้นๆ จะได้รับการบัฟเฟอร์และส่งเมื่อระบบเลิกหยุดแอปไว้ชั่วคราว
ธุรกรรมที่บัฟเฟอร์ไว้สำหรับการเรียกกลับแอปอาจล้าสมัยเมื่อแอปเลิกหยุดทำงานชั่วคราวและประมวลผลธุรกรรมเหล่านั้น บัฟเฟอร์มีขีดจำกัด หากมีข้อมูลเกินขีดจำกัด แอปผู้รับอาจขัดข้อง อย่าส่งคอลแบ็กของแอปขณะที่กระบวนการของแอปหยุดทำงานอยู่ เพื่อไม่ให้แอปมีเหตุการณ์ที่ล้าสมัยหรือบัฟเฟอร์ล้น
อยู่ระหว่างการตรวจสอบ
- คุณควรพิจารณาหยุดการส่ง App Callback ชั่วคราวขณะที่แคชกระบวนการของแอป
- คุณต้องหยุดการส่งคอลแบ็กของแอปชั่วคราวขณะที่กระบวนการของแอปหยุดชะงัก
การติดตามสถานะ
วิธีติดตามเมื่อแอปเข้าสู่หรือออกจากสถานะแคช
mActivityManager.addOnUidImportanceListener(
new UidImportanceListener() { ... },
IMPORTANCE_CACHED);
วิธีติดตามเมื่อแอปถูกหยุดทำงานหรือเลิกหยุดทำงาน
IBinder binder = <...>;
binder.addFrozenStateChangeCallback(executor, callback);
กลยุทธ์ในการกลับมาดำเนินการกับคอลแบ็กของแอป
ไม่ว่าคุณจะหยุดส่งคอลแบ็กของแอปชั่วคราวเมื่อแอปเข้าสู่สถานะแคชหรือสถานะหยุดทำงาน เมื่อแอปออกจากสถานะดังกล่าวแล้ว คุณควรกลับมาส่งคอลแบ็กที่ลงทะเบียนไว้ของแอปอีกครั้งจนกว่าแอปจะยกเลิกการลงทะเบียนคอลแบ็กหรือกระบวนการของแอปหยุดทำงาน
เช่น
IBinder binder = <...>;
bool shouldSendCallbacks = true;
binder.addFrozenStateChangeCallback(executor, (who, state) -> {
if (state == IBinder.FrozenStateChangeCallback.STATE_FROZEN) {
shouldSendCallbacks = false;
} else if (state == IBinder.FrozenStateChangeCallback.STATE_UNFROZEN) {
shouldSendCallbacks = true;
}
});
หรือจะใช้ RemoteCallbackList
ก็ได้ ซึ่งจะดูแลไม่ให้ส่งการเรียกกลับไปยังกระบวนการเป้าหมายเมื่อกระบวนการดังกล่าวหยุดทำงาน
เช่น
RemoteCallbackList<IInterface> rc =
new RemoteCallbackList.Builder<IInterface>(
RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
.setExecutor(executor)
.build();
rc.register(callback);
rc.broadcast((callback) -> callback.foo(bar));
ระบบจะเรียกใช้ callback.foo()
เฉพาะในกรณีที่กระบวนการไม่หยุดทำงาน
แอปมักจะบันทึกการอัปเดตที่ได้รับโดยใช้การเรียกกลับเป็นสแนปชอตของสถานะล่าสุด ลองพิจารณา API จําลองสําหรับแอปในการตรวจสอบเปอร์เซ็นต์แบตเตอรี่ที่เหลือ
interface BatteryListener {
void onBatteryPercentageChanged(int newPercentage);
}
ลองนึกถึงกรณีที่เหตุการณ์การเปลี่ยนแปลงสถานะหลายรายการเกิดขึ้นเมื่อแอปหยุดทำงาน เมื่อเลิกหยุดแอปชั่วคราวแล้ว คุณควรส่งเฉพาะสถานะล่าสุดไปยังแอปและทิ้งการเปลี่ยนแปลงสถานะอื่นๆ ที่ล้าสมัย การนำส่งนี้ควรเกิดขึ้นทันทีที่แอปเลิกหยุดทำงานเพื่อให้แอป "ตามทัน" ซึ่งทำได้ดังนี้
RemoteCallbackList<IInterface> rc =
new RemoteCallbackList.Builder<IInterface>(
RemoteCallbackList.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT)
.setExecutor(executor)
.build();
rc.register(callback);
rc.broadcast((callback) -> callback.onBatteryPercentageChanged(value));
ในบางกรณี คุณอาจติดตามค่าล่าสุดที่ส่งไปยังแอปเพื่อให้แอปไม่ต้องได้รับการแจ้งเตือนค่าเดียวกันเมื่อมีการเลิกตรึง
สถานะอาจแสดงเป็นข้อมูลที่ซับซ้อนมากขึ้น ลองพิจารณา API สมมติสำหรับแอปที่จะได้รับการแจ้งเตือนเกี่ยวกับอินเทอร์เฟซเครือข่าย
interface NetworkListener {
void onAvailable(Network network);
void onLost(Network network);
void onChanged(Network network);
}
เมื่อหยุดการแจ้งเตือนไปยังแอปชั่วคราว คุณควรจดจำชุดเครือข่ายและรัฐที่แอปเห็นล่าสุด เมื่อกลับมาทํางานอีกครั้ง เราขอแนะนําให้แจ้งแอปเกี่ยวกับเครือข่ายเก่าที่สูญหาย เครือข่ายใหม่ที่ใช้ได้ และเครือข่ายที่มีอยู่ซึ่งสถานะมีการเปลี่ยนแปลงตามลําดับนี้
อย่าแจ้งให้แอปทราบเกี่ยวกับเครือข่ายที่พร้อมใช้งานแล้วหายไปขณะที่การเรียกกลับถูกหยุดชั่วคราว แอปไม่ควรได้รับข้อมูลทั้งหมดของเหตุการณ์ที่เกิดขึ้นขณะที่หยุดทำงานอยู่ และเอกสารประกอบ API ไม่ควรสัญญาว่าจะส่งสตรีมเหตุการณ์อย่างต่อเนื่องนอกสถานะวงจรที่ชัดเจน ในตัวอย่างนี้ หากแอปต้องตรวจสอบความพร้อมใช้งานของเครือข่ายอย่างต่อเนื่อง แอปจะต้องอยู่ในสถานะวงจรที่ป้องกันไม่ให้แคชหรือหยุดทำงาน
สรุปแล้ว คุณควรรวมเหตุการณ์ที่เกิดขึ้นหลังจากหยุดชั่วคราวและก่อนที่จะเริ่มการแจ้งเตือนอีกครั้ง และส่งสถานะล่าสุดไปยัง App Callbacks ที่ลงทะเบียนไว้อย่างกระชับ
ข้อควรพิจารณาสำหรับเอกสารประกอบสำหรับนักพัฒนาแอป
การส่งเหตุการณ์แบบแอซิงค์อาจล่าช้าเนื่องจากผู้ส่งหยุดการส่งชั่วคราวเป็นระยะเวลาหนึ่งตามที่แสดงในส่วนก่อนหน้า หรือเนื่องจากแอปผู้รับไม่ได้รับทรัพยากรอุปกรณ์เพียงพอที่จะประมวลผลเหตุการณ์อย่างทันท่วงที
ไม่แนะนำให้นักพัฒนาแอปคาดเดาเวลาระหว่างตอนที่แอปได้รับการแจ้งเตือนเหตุการณ์กับตอนที่เหตุการณ์เกิดขึ้นจริง
สิ่งที่นักพัฒนาแอปควรทราบเกี่ยวกับการระงับ API
นักพัฒนาแอปที่คุ้นเคยกับการเรียกใช้แบบขนานที่มีโครงสร้างของ Kotlin คาดหวังลักษณะการทำงานต่อไปนี้จาก API ที่ระงับ
ฟังก์ชันที่หยุดชั่วคราวควรทํางานที่เกี่ยวข้องทั้งหมดให้เสร็จสิ้นก่อนที่จะแสดงผลลัพธ์หรือยกเว้น
ระบบจะแสดงผลลัพธ์ของการดำเนินการแบบไม่บล็อกเป็นค่าที่ฟังก์ชันแสดงผลตามปกติ และรายงานข้อผิดพลาดโดยการยกเว้น (ซึ่งมักจะหมายความว่าไม่จำเป็นต้องมีพารามิเตอร์การเรียกกลับ)
ฟังก์ชันการระงับควรเรียกใช้พารามิเตอร์ Callback เฉพาะในตำแหน่ง
ฟังก์ชันที่หยุดชั่วคราวควรทำงานที่เกี่ยวข้องทั้งหมดให้เสร็จสมบูรณ์ก่อนที่จะแสดงผล ดังนั้นจึงไม่ควรเรียกใช้ Callback หรือพารามิเตอร์ฟังก์ชันอื่นๆ ที่ระบุไว้ หรือเก็บการอ้างอิงไว้หลังจากที่ฟังก์ชันที่หยุดชั่วคราวแสดงผลแล้ว
ฟังก์ชันการระงับที่ยอมรับพารามิเตอร์การเรียกกลับควรเก็บรักษาบริบท เว้นแต่จะระบุไว้เป็นอย่างอื่น
การเรียกฟังก์ชันในฟังก์ชันที่หยุดชั่วคราวจะทำให้ฟังก์ชันนั้นทำงานใน
CoroutineContext
ของผู้เรียก เนื่องจากฟังก์ชันที่ระงับควรทํางานทั้งหมดที่เกี่ยวข้องให้เสร็จสิ้นก่อนที่จะแสดงผลหรือยกเว้น และควรเรียกใช้พารามิเตอร์การเรียกคืนเฉพาะในตำแหน่งนั้นๆ เท่านั้น ค่าเริ่มต้นที่คาดหวังคือระบบจะเรียกใช้การเรียกคืนดังกล่าวใน CoroutineContext
ที่เรียกใช้โดยใช้ตัวจัดเตรียมที่เกี่ยวข้องด้วย หากวัตถุประสงค์ของ API คือเรียกใช้การเรียกกลับนอกการเรียกCoroutineContext
ก็ควรบันทึกลักษณะการทำงานนี้อย่างชัดเจน
ฟังก์ชันการระงับควรรองรับการยกเลิกงาน kotlinx.coroutines
ฟังก์ชันการระงับที่เสนอควรทำงานร่วมกับการยกเลิกงานตามที่ kotlinx.coroutines
กำหนด หากมีการยกเลิกงานการเรียกของการดำเนินการที่กำลังดำเนินอยู่ ฟังก์ชันควรกลับมาทำงานต่อด้วย CancellationException
โดยเร็วที่สุดเพื่อให้ผู้เรียกสามารถล้างข้อมูลและดำเนินการต่อโดยเร็วที่สุด suspendCancellableCoroutine
และ API อื่นๆ สำหรับการระงับที่ kotlinx.coroutines
ให้บริการจะจัดการเรื่องนี้โดยอัตโนมัติ โดยทั่วไป การใช้งานไลบรารีไม่ควรใช้ suspendCoroutine
โดยตรง เนื่องจากไม่รองรับลักษณะการยกเลิกนี้โดยค่าเริ่มต้น
ฟังก์ชันที่ระงับการดำเนินการที่บล็อกในเบื้องหลัง (ไม่ใช่เธรดหลักหรือ UI) ต้องมีวิธีกำหนดค่าตัวจัดเตรียมที่ใช้
ไม่แนะนําให้ทําให้ฟังก์ชันการบล็อกหยุดทั้งหมดเพื่อสลับเธรด
การเรียกใช้ฟังก์ชันการระงับไม่ควรส่งผลให้มีการสร้างเธรดเพิ่มเติมโดยไม่อนุญาตให้นักพัฒนาแอประบุเธรดหรือพูลเธรดของตนเองเพื่อดำเนินการดังกล่าว ตัวอย่างเช่น ตัวสร้างอาจยอมรับ CoroutineContext
ที่ใช้เพื่อดําเนินการเบื้องหลังสําหรับเมธอดของคลาส
ฟังก์ชันการระงับที่จะยอมรับพารามิเตอร์ CoroutineContext
หรือ Dispatcher
(ไม่บังคับ) เพื่อเปลี่ยนไปใช้ตัวจัดเตรียมนั้นเพื่อทำงานแบบบล็อกควรแสดงฟังก์ชันการบล็อกที่อยู่เบื้องหลังแทน และแนะนำให้นักพัฒนาแอปที่เรียกใช้ใช้การเรียกใช้ withContext ของตนเองเพื่อส่งงานไปยังตัวจัดเตรียมที่เลือก
คลาสที่เปิดใช้งานโคโรทีน
คลาสที่เปิดใช้โคโริวทีนต้องมี CoroutineScope
เพื่อดำเนินการเปิดใช้งานเหล่านั้น การปฏิบัติตามหลักการการทำงานพร้อมกันแบบมีโครงสร้างจะนําไปสู่รูปแบบโครงสร้างต่อไปนี้สําหรับการได้รับและจัดการขอบเขตนั้น
ก่อนเขียนคลาสที่เปิดใช้งานงานพร้อมกันในขอบเขตอื่น ให้พิจารณารูปแบบอื่นๆ ดังนี้
class MyClass {
private val requests = Channel<MyRequest>(Channel.UNLIMITED)
suspend fun handleRequests() {
coroutineScope {
for (request in requests) {
// Allow requests to be processed concurrently;
// alternatively, omit the [launch] and outer [coroutineScope]
// to process requests serially
launch {
processRequest(request)
}
}
}
}
fun submitRequest(request: MyRequest) {
requests.trySend(request).getOrThrow()
}
}
การเปิดเผย suspend fun
เพื่อทํางานพร้อมกันช่วยให้ผู้เรียกใช้เรียกใช้การดำเนินการในบริบทของตนเองได้ จึงไม่จำเป็นต้องให้ MyClass
จัดการ CoroutineScope
การทำให้การประมวลผลคําขอเป็นแบบอนุกรมจะง่ายขึ้น และสถานะมักจะอยู่ในรูปแบบตัวแปรภายในของ handleRequests
แทนที่จะเป็นพร็อพเพอร์ตี้ของคลาสที่ต้องมีการซิงค์เพิ่มเติม
คลาสที่จัดการโคโริวทีนควรแสดงเมธอด close และ cancel
คลาสที่เปิดใช้โคโริวทีนเป็นรายละเอียดการใช้งานต้องระบุวิธีปิดงานที่เกิดขึ้นพร้อมกันอย่างต่อเนื่องอย่างเรียบร้อยเพื่อไม่ให้งานที่เกิดขึ้นพร้อมกันซึ่งควบคุมไม่ได้รั่วไหลไปยังขอบเขตหลัก โดยทั่วไป รูปแบบการสร้าง Job
ย่อยของ CoroutineContext
ที่ระบุมีดังนี้
private val myJob = Job(parent = `CoroutineContext`[Job])
private val myScope = CoroutineScope(`CoroutineContext` + myJob)
fun cancel() {
myJob.cancel()
}
นอกจากนี้ คุณยังระบุเมธอด join()
เพื่อให้โค้ดของผู้ใช้รอให้งานที่ทำพร้อมกันซึ่งออบเจ็กต์กำลังดำเนินการอยู่เสร็จสิ้นได้ด้วย
(ซึ่งอาจรวมถึงการล้างข้อมูลโดยยกเลิกการดำเนินการ)
suspend fun join() {
myJob.join()
}
การตั้งชื่อการดำเนินการของเทอร์มินัล
ชื่อที่ใช้สำหรับเมธอดที่ปิดงานพร้อมกันซึ่งเป็นเจ้าของโดยออบเจ็กต์ที่ยังคงดําเนินการอยู่อย่างเรียบร้อยควรแสดงถึงสัญญาด้านลักษณะการทํางานของวิธีปิด
ใช้ close()
เมื่อการดำเนินการที่อยู่ระหว่างดำเนินการอาจเสร็จสมบูรณ์ แต่การดำเนินการใหม่จะเริ่มต้นไม่ได้หลังจากการเรียกใช้ close()
แสดงผล
ใช้ cancel()
เมื่อการดำเนินการที่อยู่ระหว่างดำเนินการอาจถูกยกเลิกก่อนที่จะเสร็จสมบูรณ์
การดำเนินการใหม่จะเริ่มขึ้นไม่ได้หลังจากที่การเรียก cancel()
แสดงผล
ตัวสร้างคลาสยอมรับ CoroutineContext ไม่ใช่ CoroutineScope
เมื่อระบบไม่อนุญาตให้เปิดใช้งานออบเจ็กต์โดยตรงในสโคปหลักที่ระบุ ความเหมาะสมของ CoroutineScope
ในฐานะพารามิเตอร์คอนสตรัคเตอร์จะลดลงดังนี้
// Don't do this
class MyClass(scope: CoroutineScope) {
private val myJob = Job(parent = scope.`CoroutineContext`[Job])
private val myScope = CoroutineScope(scope.`CoroutineContext` + myJob)
// ... the [scope] constructor parameter is never used again
}
CoroutineScope
จะกลายเป็น Wrapper ที่ไม่จำเป็นและทำให้เข้าใจผิด ซึ่งในบาง Use Case อาจสร้างขึ้นเพื่อส่งผ่านเป็นพารามิเตอร์คอนสตรัคเตอร์เท่านั้น แต่จะถูกทิ้งไป
// Don't do this; just pass the context
val myObject = MyClass(CoroutineScope(parentScope.`CoroutineContext` + Dispatchers.IO))
พารามิเตอร์ CoroutineContext จะมีค่าเริ่มต้นเป็น EmptyCoroutineContext
เมื่อพารามิเตอร์ CoroutineContext
ไม่บังคับปรากฏในแพลตฟอร์ม API ค่าเริ่มต้นต้องเป็นเงื่อนไข Empty`CoroutineContext`
วิธีนี้ช่วยให้คุณกำหนดลักษณะการทํางานของ API ได้ดีขึ้น เนื่องจากระบบจะถือว่าค่า Empty`CoroutineContext`
จากตัวแฮนเดิลการเรียกใช้เหมือนกับการยอมรับค่าเริ่มต้น
class MyOuterClass(
`CoroutineContext`: `CoroutineContext` = Empty`CoroutineContext`
) {
private val innerObject = MyInnerClass(`CoroutineContext`)
// ...
}
class MyInnerClass(
`CoroutineContext`: `CoroutineContext` = Empty`CoroutineContext`
) {
private val job = Job(parent = `CoroutineContext`[Job])
private val scope = CoroutineScope(`CoroutineContext` + job)
// ...
}