หลักเกณฑ์ API แบบไม่บล็อกและแบบ Async ของ Android

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 แบบไม่พร้อมกันซึ่งการเรียกกลับมาจากกระบวนการของระบบและส่งไปยังแอป ให้พิจารณาสิ่งต่อไปนี้

  1. กระบวนการและวงจรชีวิตของแอป: กระบวนการของแอปผู้รับอาจอยู่ในสถานะแคชไว้
  2. การแช่แข็งแอปที่แคชไว้: กระบวนการของแอปผู้รับอาจหยุดทำงาน

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

    // ...
}