API ที่ไม่บล็อกจะขอให้ดำเนินการและส่งคืนการควบคุมไปยัง เธรดที่เรียกใช้เพื่อให้เธรดดังกล่าวสามารถทำงานอื่นๆ ได้ก่อนที่ การดำเนินการที่ขอจะเสร็จสมบูรณ์ API เหล่านี้มีประโยชน์ในกรณีที่งานที่ขออาจอยู่ระหว่างดำเนินการ หรืออาจต้องรอให้ I/O หรือ IPC เสร็จสมบูรณ์ ความพร้อมใช้งานของทรัพยากรระบบที่มีการแข่งขันสูง หรืออินพุตของผู้ใช้ก่อนที่จะดำเนินการต่อได้ โดยเฉพาะอย่างยิ่ง API ที่ออกแบบมาอย่างดีจะช่วยให้ยกเลิกการดำเนินการ ที่กำลังดำเนินการอยู่และหยุดการทำงานในนามของผู้โทรเดิมได้ ซึ่งจะช่วยรักษาประสิทธิภาพของระบบและอายุการใช้งานแบตเตอรี่เมื่อไม่จำเป็นต้องดำเนินการอีกต่อไป
API แบบไม่พร้อมกันเป็นวิธีหนึ่งในการทำให้เกิดลักษณะการทำงานแบบไม่บล็อก API แบบไม่พร้อมกัน ยอมรับรูปแบบการดำเนินการต่อหรือการเรียกกลับบางอย่างที่จะได้รับการแจ้งเตือนเมื่อการดำเนินการ เสร็จสมบูรณ์ หรือเมื่อมีเหตุการณ์อื่นๆ ในระหว่างความคืบหน้าของการดำเนินการ
การเขียน API แบบไม่พร้อมกันมีแรงจูงใจหลักๆ 2 ประการ ดังนี้
- การดำเนินการหลายอย่างพร้อมกัน โดยการดำเนินการที่ N จะต้องเริ่มต้นก่อนที่การดำเนินการที่ N-1 จะเสร็จสมบูรณ์
- หลีกเลี่ยงการบล็อกเธรดการเรียกจนกว่าการดำเนินการจะเสร็จสมบูรณ์
Kotlin สนับสนุนการทำงานพร้อมกันที่มีโครงสร้างอย่างยิ่ง ซึ่งเป็นชุดหลักการและ API ที่สร้างขึ้นบนฟังก์ชันระงับซึ่งแยกการดำเนินการโค้ดแบบซิงโครนัสและแบบอะซิงโครนัสออกจากลักษณะการทำงานที่บล็อกเธรด ฟังก์ชันระงับเป็นแบบไม่บล็อกและซิงโครนัส
ฟังก์ชันระงับ
- อย่าบล็อกเธรดการเรียกของฟังก์ชันดังกล่าว แต่ให้ส่งต่อเธรดการดำเนินการเป็น รายละเอียดการใช้งานขณะรอผลลัพธ์ของการดำเนินการที่ดำเนินการ ที่อื่นแทน
- ดำเนินการแบบพร้อมกันและไม่กำหนดให้ผู้เรียกใช้ API ที่ไม่บล็อก ดำเนินการต่อพร้อมกันกับงานที่ไม่บล็อกซึ่งเริ่มต้นโดยการเรียกใช้ API
หน้านี้จะอธิบายเกณฑ์พื้นฐานขั้นต่ำที่นักพัฒนาแอปคาดหวังได้อย่างปลอดภัย เมื่อทำงานกับ API แบบไม่บล็อกและแบบอะซิงโครนัส ตามด้วยชุด สูตรสำหรับการเขียน API ที่ตรงตามความคาดหวังเหล่านี้ในภาษา Kotlin หรือ Java ในแพลตฟอร์ม Android หรือไลบรารี Jetpack หากไม่แน่ใจ ให้พิจารณา ความคาดหวังของนักพัฒนาแอปเป็นข้อกำหนดสำหรับ API Surface ใหม่
สิ่งที่นักพัฒนาแอปคาดหวังจาก API แบบไม่พร้อมกัน
ความคาดหวังต่อไปนี้เขียนขึ้นจากมุมมองของ API ที่ไม่ถูกระงับ เว้นแต่จะระบุไว้เป็นอย่างอื่น
โดยปกติแล้ว API ที่ยอมรับการเรียกกลับจะเป็นแบบไม่พร้อมกัน
หาก API ยอมรับการเรียกกลับที่ไม่ได้ระบุไว้ว่าจะเรียกใช้ในที่เท่านั้น (กล่าวคือ เรียกใช้โดยเธรดที่เรียกเท่านั้นก่อนที่การเรียก API เองจะกลับมา) ระบบจะถือว่า API เป็นแบบไม่พร้อมกัน และ API นั้นควรเป็นไปตามความคาดหวังอื่นๆ ทั้งหมดที่ระบุไว้ในส่วนต่อไปนี้
ตัวอย่างของ Callback ที่เรียกใช้ในที่เดียวเสมอคือฟังก์ชัน Map หรือ Filter ระดับสูงกว่า ซึ่งเรียกใช้ Mapper หรือ Predicate ในแต่ละรายการในคอลเล็กชันก่อนที่จะแสดงผล
API แบบไม่พร้อมกันควรแสดงผลโดยเร็วที่สุด
นักพัฒนาแอปคาดหวังว่า API แบบไม่พร้อมกันจะไม่บล็อกและจะตอบกลับอย่างรวดเร็วหลังจาก เริ่มคำขอสำหรับการดำเนินการ คุณควรเรียกใช้ API แบบไม่พร้อมกันได้อย่างปลอดภัยทุกเมื่อ และการเรียกใช้ API แบบไม่พร้อมกันไม่ควรทำให้เกิดเฟรมที่กระตุกหรือ ANR
แพลตฟอร์มหรือไลบรารีสามารถทริกเกอร์สัญญาณการดำเนินการและวงจรการใช้งานจำนวนมากได้ตามต้องการ และการคาดหวังให้นักพัฒนาแอปมีความรู้ในระดับโลกเกี่ยวกับเว็บไซต์ที่อาจเรียกใช้โค้ดของตนทั้งหมดนั้นเป็นไปไม่ได้ เช่น Fragment
สามารถเพิ่มลงใน FragmentManager
ในธุรกรรมแบบซิงโครนัสในการตอบสนอง
ต่อการวัดและเลย์เอาต์ View
เมื่อต้องป้อนข้อมูลเนื้อหาแอปเพื่อเติม
พื้นที่ว่าง (เช่น RecyclerView
) LifecycleObserver
ที่ตอบสนองต่อ
การเรียกกลับวงจร onStart
ของ Fragment นี้อาจดำเนินการเริ่มต้นแบบครั้งเดียว
ที่นี่ได้อย่างสมเหตุสมผล และอาจอยู่ในเส้นทางโค้ดที่สำคัญสำหรับการสร้าง
เฟรมภาพเคลื่อนไหวที่ไม่มีการกระตุก นักพัฒนาแอปควรมั่นใจเสมอว่าการเรียกใช้ API แบบไม่พร้อมกันใดๆ เพื่อตอบสนองต่อการเรียกกลับของวงจรการทำงานประเภทนี้
จะไม่ทำให้เกิดเฟรมที่กระตุก
ซึ่งหมายความว่างานที่ API แบบไม่พร้อมกันดำเนินการก่อนที่จะส่งคืนจะต้องมี น้ำหนักเบามาก โดยอาจเป็นการสร้างบันทึกของคำขอและ Callback ที่เกี่ยวข้อง และลงทะเบียนกับเครื่องมือการดำเนินการที่ทำงานดังกล่าว หากการลงทะเบียนสำหรับการดำเนินการแบบไม่พร้อมกันต้องใช้ IPC การใช้งาน API ควร ใช้มาตรการที่จำเป็นเพื่อตอบสนองความคาดหวังของนักพัฒนาซอฟต์แวร์นี้ ซึ่งอาจรวมถึงสิ่งต่อไปนี้อย่างน้อย 1 รายการ
- การใช้ IPC พื้นฐานเป็นการเรียกใช้ Binder ทางเดียว
- การโทรแบบสองทางไปยังเซิร์ฟเวอร์ของระบบซึ่งการลงทะเบียนให้เสร็จสมบูรณ์ไม่จำเป็นต้องใช้การล็อกที่มีการแข่งขันสูง
- โพสต์คำขอไปยัง Worker Thread ในกระบวนการของแอปเพื่อทำการลงทะเบียนที่บล็อกผ่าน IPC
API แบบไม่พร้อมกันควรแสดงผลเป็น void และจะส่งข้อยกเว้นสำหรับอาร์กิวเมนต์ที่ไม่ถูกต้องเท่านั้น
API แบบไม่พร้อมกันควรรายงานผลลัพธ์ทั้งหมดของการดำเนินการที่ขอไปยัง การเรียกกลับที่ระบุ ซึ่งช่วยให้นักพัฒนาซอฟต์แวร์สามารถใช้เส้นทางโค้ดเดียวเพื่อจัดการข้อผิดพลาดและทำให้แอปประสบความสำเร็จได้
API แบบไม่พร้อมกันอาจตรวจสอบอาร์กิวเมนต์ว่ามีค่าเป็น Null หรือไม่ และส่ง NullPointerException
หรือ
ตรวจสอบว่าอาร์กิวเมนต์ที่ระบุอยู่ในช่วงที่ถูกต้องหรือไม่ และส่ง
IllegalArgumentException
ตัวอย่างเช่น สำหรับฟังก์ชันที่ยอมรับ float
ในขอบเขตตั้งแต่ 0
ถึง 1f
ฟังก์ชันอาจตรวจสอบว่าพารามิเตอร์อยู่ภายใน
ขอบเขตนี้หรือไม่ และแสดง IllegalArgumentException
หากอยู่นอกขอบเขต หรืออาจตรวจสอบString
แบบสั้นเพื่อดูว่าสอดคล้องกับรูปแบบที่ถูกต้องหรือไม่ เช่น
ตัวอักษรและตัวเลขเท่านั้น (โปรดทราบว่าเซิร์ฟเวอร์ระบบไม่ควรเชื่อถือกระบวนการของแอป
บริการของระบบควรทำซ้ำการตรวจสอบเหล่านี้ในบริการของระบบ
ด้วยตนเอง)
ควรรายงานข้อผิดพลาดอื่นๆ ทั้งหมดไปยังการเรียกกลับที่ระบุ ซึ่งรวมถึงแต่ไม่จำกัดเพียง
- การดำเนินการที่ขอไม่สำเร็จ
- ข้อยกเว้นด้านความปลอดภัยสำหรับการให้สิทธิ์หรือสิทธิ์ที่จำเป็นในการ ดำเนินการให้เสร็จสมบูรณ์
- เกินโควต้าสำหรับการดำเนินการ
- กระบวนการของแอปไม่ได้ "อยู่เบื้องหน้า" มากพอที่จะดำเนินการ
- ฮาร์ดแวร์ที่จำเป็นถูกตัดการเชื่อมต่อ
- เครือข่ายล้มเหลว
- หมดเวลา
- Binder สิ้นสุดการทำงานหรือกระบวนการระยะไกลไม่พร้อมใช้งาน
API แบบไม่พร้อมกันควรมีกลไกการยกเลิก
API แบบไม่พร้อมกันควรมีวิธีระบุให้การดำเนินการที่กำลังทำงานทราบว่า ผู้เรียกไม่สนใจผลลัพธ์อีกต่อไป การดำเนินการยกเลิกนี้ควรส่งสัญญาณ 2 อย่างต่อไปนี้
ควรปล่อยการอ้างอิงที่ชัดเจนถึง Callback ที่ผู้เรียกใช้ระบุ
การเรียกกลับที่ระบุไว้ใน API แบบอะซิงโครนัสอาจมีการอ้างอิงแบบฮาร์ดโค้ดไปยังกราฟออบเจ็กต์ขนาดใหญ่ และงานที่กำลังดำเนินการซึ่งมีการอ้างอิงแบบฮาร์ดโค้ดไปยังการเรียกกลับนั้นอาจทำให้กราฟออบเจ็กต์เหล่านั้นไม่ถูกเก็บขยะ การปล่อยการอ้างอิงการเรียกกลับเหล่านี้เมื่อมีการยกเลิกจะทำให้กราฟออบเจ็กต์เหล่านี้อาจมีสิทธิ์สำหรับการเก็บขยะเร็วกว่าในกรณีที่อนุญาตให้งานทำงานจนเสร็จ
เครื่องมือการดำเนินการที่ทำงานให้กับผู้โทรอาจหยุดงานนั้น
งานที่เริ่มต้นโดยการเรียก API แบบไม่พร้อมกันอาจมีค่าใช้จ่ายสูงในด้านการใช้พลังงานหรือ ทรัพยากรอื่นๆ ของระบบ API ที่อนุญาตให้ผู้โทรส่งสัญญาณเมื่อไม่จำเป็นต้องดำเนินการนี้อีกต่อไปจะอนุญาตให้หยุดการดำเนินการนั้นก่อนที่จะใช้ทรัพยากรระบบเพิ่มเติม
ข้อควรพิจารณาพิเศษสำหรับแอปที่แคชไว้หรือแอปที่ถูกระงับ
เมื่อออกแบบ API แบบไม่พร้อมกันซึ่งมีการเรียกกลับที่มาจากกระบวนการของระบบ และส่งไปยังแอป ให้พิจารณาสิ่งต่อไปนี้
- กระบวนการและวงจรชีวิตของแอป: กระบวนการของแอปผู้รับอาจอยู่ในสถานะแคช
- เครื่องมือหยุดทำงานของแอปที่แคชไว้: กระบวนการของแอปผู้รับอาจถูกหยุดทำงาน
เมื่อกระบวนการของแอปเข้าสู่สถานะแคช หมายความว่ากระบวนการนั้นไม่ได้โฮสต์คอมโพเนนต์ที่ผู้ใช้มองเห็น เช่น กิจกรรมและบริการ ระบบจะเก็บแอปไว้ในหน่วยความจำในกรณีที่แอปกลับมาปรากฏต่อผู้ใช้อีกครั้ง แต่ในระหว่างนี้แอปไม่ควรทำงาน ในกรณีส่วนใหญ่ คุณควรหยุดการเรียกกลับของแอปชั่วคราวเมื่อแอปเข้าสู่สถานะแคช และกลับมาทำงานต่อเมื่อแอปออกจากสถานะแคช เพื่อไม่ให้เกิดการทำงานในกระบวนการของแอปที่แคชไว้
ระบบอาจหยุดแอปที่แคชไว้ชั่วคราวด้วย เมื่อแอปถูกระงับ แอปจะได้รับเวลา CPU เป็น 0 และไม่สามารถทำงานใดๆ ได้เลย ระบบจะบัฟเฟอร์การเรียกกลับที่ลงทะเบียนไว้ของแอปนั้นและส่งเมื่อแอปเลิกหยุดทำงาน
ธุรกรรมที่บัฟเฟอร์ไปยังการเรียกกลับของแอปอาจล้าสมัยเมื่อแอป เลิกตรึงและประมวลผลธุรกรรมเหล่านั้น บัฟเฟอร์มีขีดจำกัด และหากมีข้อมูลมากเกินไปจะทำให้แอปของผู้รับขัดข้อง อย่าเรียกใช้แอป 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 ไม่ควรรับประกันว่าจะส่งสตรีมเหตุการณ์อย่างต่อเนื่องนอกสถานะวงจรที่ชัดเจน ใน ตัวอย่างนี้ หากแอปต้องตรวจสอบความพร้อมใช้งานของเครือข่ายอย่างต่อเนื่อง แอปจะต้องอยู่ในสถานะวงจรของแอปที่ป้องกันไม่ให้แอปถูกแคชหรือ หยุดชั่วคราว
ในการตรวจสอบ คุณควรรวมเหตุการณ์ที่เกิดขึ้นหลังจากหยุดชั่วคราวและก่อนที่จะกลับมาส่งการแจ้งเตือนอีกครั้ง และส่งสถานะล่าสุดไปยังการเรียกกลับของแอปที่ลงทะเบียนอย่างกระชับ
ข้อควรพิจารณาสำหรับเอกสารประกอบสำหรับนักพัฒนาแอป
การนำส่งเหตุการณ์แบบไม่พร้อมกันอาจล่าช้าเนื่องจากผู้ส่งหยุดชั่วคราว การนำส่งเป็นระยะเวลาหนึ่งตามที่แสดงในส่วนก่อนหน้า หรือเนื่องจาก แอปผู้รับไม่ได้รับทรัพยากรของอุปกรณ์เพียงพอที่จะประมวลผลเหตุการณ์ในลักษณะ ที่ทันท่วงที
ไม่สนับสนุนให้นักพัฒนาแอปคาดเดาเวลาตั้งแต่ตอนที่แอปได้รับการแจ้งเตือนเหตุการณ์จนถึงเวลาที่เกิดเหตุการณ์จริง
สิ่งที่นักพัฒนาแอปคาดหวังเมื่อมีการระงับ API
นักพัฒนาแอปที่คุ้นเคยกับการทำงานพร้อมกันแบบมีโครงสร้างของ Kotlin คาดหวังพฤติกรรมต่อไปนี้จาก API ที่ระงับการทำงาน
ฟังก์ชันที่ระงับควรทำงานที่เกี่ยวข้องทั้งหมดให้เสร็จก่อนที่จะส่งคืนหรือส่งข้อยกเว้น
ระบบจะแสดงผลลัพธ์ของการดำเนินการที่ไม่บล็อกเป็นค่าการแสดงผลของฟังก์ชันปกติ และรายงานข้อผิดพลาดโดยการส่งข้อยกเว้น (ซึ่งมักหมายความว่าไม่จำเป็นต้องใช้พารามิเตอร์ การเรียกกลับ)
ฟังก์ชันระงับควรเรียกใช้พารามิเตอร์ Callback ในที่เดียวเท่านั้น
ฟังก์ชันระงับควรทำงานที่เกี่ยวข้องทั้งหมดให้เสร็จสมบูรณ์ก่อนที่จะส่งคืนเสมอ ดังนั้นจึงไม่ควรเรียกใช้ Callback ที่ระบุหรือพารามิเตอร์ฟังก์ชันอื่นๆ หรือ เก็บการอ้างอิงไว้หลังจากที่ฟังก์ชันระงับส่งคืนแล้ว
ฟังก์ชันระงับที่ยอมรับพารามิเตอร์การเรียกกลับควรคงบริบทไว้ เว้นแต่จะระบุไว้เป็นอย่างอื่น
การเรียกใช้ฟังก์ชันในฟังก์ชันระงับจะทำให้ฟังก์ชันนั้นทำงานใน
CoroutineContext
ของผู้เรียก เนื่องจากฟังก์ชันระงับควรทำงานที่เกี่ยวข้องทั้งหมดให้เสร็จสิ้นก่อนที่จะส่งคืนหรือส่งข้อยกเว้น และควรเรียกใช้พารามิเตอร์การเรียกกลับในที่เดียวเท่านั้น ค่าเริ่มต้นที่คาดไว้คือการเรียกกลับดังกล่าวจะทำงานใน CoroutineContext
ที่เรียกใช้โดยใช้ Dispatcher ที่เชื่อมโยง หากวัตถุประสงค์ของ API คือการเรียกใช้ Callback นอกบริบทการเรียกใช้ CoroutineContext
คุณควรบันทึกพฤติกรรมนี้อย่างชัดเจน
ฟังก์ชันระงับควรรองรับการยกเลิกงาน kotlinx.coroutines
ฟังก์ชันระงับใดๆ ที่เสนอควรทำงานร่วมกับการยกเลิกงานตามที่กำหนดโดย kotlinx.coroutines
หากมีการยกเลิกงานการเรียกของโอเปอเรชันที่กำลังดำเนินการ ฟังก์ชันควรกลับมาทำงานต่อด้วย CancellationException
โดยเร็วที่สุดเพื่อให้ผู้โทรสามารถล้างข้อมูลและดำเนินการต่อได้โดยเร็วที่สุด suspendCancellableCoroutine
และ API การระงับอื่นๆ ที่ kotlinx.coroutines
นำเสนอจะจัดการเรื่องนี้โดยอัตโนมัติ โดยทั่วไปแล้ว การติดตั้งใช้งานไลบรารีไม่ควรใช้ suspendCoroutine
โดยตรง เนื่องจากไม่รองรับลักษณะการยกเลิกนี้โดยค่าเริ่มต้น
ฟังก์ชันที่ทำงานบล็อกในเบื้องหลัง (ไม่ใช่เธรดหลักหรือ UI) ต้องมีวิธีในการกำหนดค่า Dispatcher ที่ใช้
ไม่แนะนำให้ใช้ฟังก์ชันบล็อกเพื่อระงับทั้งหมดเพื่อ สลับเธรด
การเรียกฟังก์ชันระงับไม่ควรส่งผลให้มีการสร้างเธรดเพิ่มเติม
โดยไม่ได้รับอนุญาตให้นักพัฒนาแอปจัดหาเธรดหรือพูลเธรดของตนเอง
เพื่อทำงานดังกล่าว ตัวอย่างเช่น เครื่องมือสร้างอาจยอมรับ
CoroutineContext
ที่ใช้เพื่อทำงานเบื้องหลังสำหรับเมธอดของคลาส
ฟังก์ชันระงับที่จะยอมรับพารามิเตอร์ CoroutineContext
หรือ
Dispatcher
ที่ไม่บังคับเฉพาะเพื่อเปลี่ยนไปใช้ Dispatcher นั้นเพื่อดำเนินการบล็อก
ควรแสดงฟังก์ชันการบล็อกพื้นฐานแทน และแนะนำให้
นักพัฒนาแอปที่เรียกใช้ใช้การเรียกของตนเองกับ withContext เพื่อส่งงานไปยัง
Dispatcher ที่เลือก
คลาสที่เปิดตัวโครูทีน
คลาสที่เปิดใช้โครูทีนต้องมี CoroutineScope
เพื่อดำเนินการเปิดใช้เหล่านั้น การปฏิบัติตามหลักการของ Structured Concurrency หมายถึงรูปแบบโครงสร้างต่อไปนี้สำหรับการรับและจัดการขอบเขตดังกล่าว
ก่อนที่จะเขียนคลาสที่เปิดใช้เทสพร้อมกันในขอบเขตอื่น ให้พิจารณารูปแบบอื่นต่อไปนี้
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
แทนที่จะเป็นพร็อพเพอร์ตี้ของคลาส
ซึ่งจะต้องมีการซิงค์เพิ่มเติม
คลาสที่จัดการโครูทีนควรแสดงเมธอดปิดและยกเลิก
คลาสที่เปิดใช้โครูทีนเป็นรายละเอียดการใช้งานต้องมีวิธีปิดงานพร้อมกันที่กำลังดำเนินการอยู่เหล่านั้นอย่างสะอาด เพื่อไม่ให้มีการรั่วไหลของงานพร้อมกันที่ไม่ได้ควบคุมไปยังขอบเขตระดับบน โดยปกติแล้ว การดำเนินการนี้จะอยู่ในรูปแบบ
ของการสร้าง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 ที่ไม่จำเป็นและทำให้เข้าใจผิด ซึ่งในบาง
กรณีการใช้งานอาจสร้างขึ้นเพื่อส่งผ่านเป็นพารามิเตอร์ของตัวสร้างเท่านั้น แล้วจึง
ทิ้งไป
// Don't do this; just pass the context
val myObject = MyClass(CoroutineScope(parentScope.`CoroutineContext` + Dispatchers.IO))
พารามิเตอร์ CoroutineContext จะมีค่าเริ่มต้นเป็น EmptyCoroutineContext
เมื่อพารามิเตอร์ CoroutineContext
ที่ไม่บังคับปรากฏใน API Surface ค่าเริ่มต้นต้องเป็น Empty`CoroutineContext`
Sentinel ซึ่งช่วยให้
การทำงานของ 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)
// ...
}