تطلب واجهات برمجة التطبيقات غير الحظرية تنفيذ العمل ثم تعيد التحكّم إلى سلسلة المحادثات التي تم استدعاؤها حتى تتمكّن من تنفيذ عمل آخر قبل إكمال العملية المطلوبة. تكون واجهات برمجة التطبيقات هذه مفيدة في الحالات التي قد يكون فيها العمل المطلوب مستمرًا أو قد يتطلّب انتظار اكتمال عمليات الإدخال/الإخراج أو الاتصال بين العمليات (IPC) أو توفُّر موارد النظام التي يتم التنافس عليها بشكل كبير أو إدخال المستخدم قبل أن يتمكّن العمل من المتابعة. توفّر واجهات برمجة التطبيقات المصمَّمة بشكل جيد طريقة لإلغاء العملية الجارية وإيقاف تنفيذ العمل نيابةً عن المتصل الأصلي، ما يحافظ على سلامة النظام وعمر البطارية عندما لا تكون العملية مطلوبة.
تُعدّ واجهات برمجة التطبيقات غير المتزامنة إحدى طرق تحقيق السلوك غير الحظر. تقبل واجهات برمجة التطبيقات غير المتزامنة بعض أشكال الاستمرار أو معاودة الاتصال التي يتم إعلامها عند اكتمال العملية، أو عند وقوع أحداث أخرى أثناء تقدّم العملية.
هناك سببان رئيسيان لكتابة واجهة برمجة تطبيقات غير متزامنة:
- تنفيذ عمليات متعددة في الوقت نفسه، حيث يجب بدء العملية رقم N قبل اكتمال العملية رقم N-1
- تجنُّب حظر سلسلة محادثات مكالمة إلى أن تكتمل عملية ما
تشجّع لغة Kotlin بشدة على استخدام التزامن المنظَّم، وهو سلسلة من المبادئ وواجهات برمجة التطبيقات المستندة إلى الدوال المعلقة التي تفصل التنفيذ المتزامن وغير المتزامن للرمز عن السلوك الذي يحظر سلاسل المحادثات. تكون الدوال المعلقة غير حاصرة ومتزامنة.
تعليق الوظائف:
- لا تحظر سلسلة استدعاءاتها، بل أوقف سلسلة التنفيذ مؤقتًا كجزء من تفاصيل التنفيذ أثناء انتظار نتائج العمليات التي يتم تنفيذها في مكان آخر.
- التنفيذ بشكل متزامن وعدم اشتراط استمرار المتصل بواجهة برمجة التطبيقات غير الحظر في التنفيذ بشكل متزامن مع العمل غير الحظر الذي بدأته عملية طلب البيانات من واجهة برمجة التطبيقات
توضّح هذه الصفحة الحد الأدنى من التوقعات التي يمكن للمطوّرين وضعها بأمان عند استخدام واجهات برمجة التطبيقات غير الحظرية وغير المتزامنة، يلي ذلك سلسلة من الوصفات لإنشاء واجهات برمجة تطبيقات تستوفي هذه التوقعات في لغة Kotlin أو Java، وذلك في نظام Android الأساسي أو مكتبات Jetpack. في حال الشك، يجب مراعاة توقعات المطوّرين كمتطلبات لأي مساحة عرض جديدة لواجهة برمجة التطبيقات.
توقّعات المطوّرين بشأن واجهات برمجة التطبيقات غير المتزامنة
تمت كتابة التوقعات التالية من منظور واجهات برمجة التطبيقات غير المعلقة ما لم يُذكر خلاف ذلك.
عادةً ما تكون واجهات برمجة التطبيقات التي تقبل عمليات رد الاتصال غير متزامنة
إذا كانت إحدى واجهات برمجة التطبيقات تقبل دالة ردّ غير موثّقة على أنّها لا يتم استدعاؤها إلا في مكانها (أي يتم استدعاؤها فقط من خلال سلسلة التعليمات التي تستدعيها قبل أن يعود طلب واجهة برمجة التطبيقات نفسه)، يُفترض أنّ واجهة برمجة التطبيقات غير متزامنة ويجب أن تستوفي جميع التوقّعات الأخرى الموضّحة في الأقسام التالية.
من الأمثلة على الدوال التي يتم استدعاؤها في مكانها فقط دالة الخريطة أو الفلتر ذات الترتيب الأعلى التي تستدعي دالة الخريطة أو دالة التوقع على كل عنصر في مجموعة قبل إرجاعها.
يجب أن تعرض واجهات برمجة التطبيقات غير المتزامنة النتائج بأسرع ما يمكن
يتوقّع المطوّرون أن تكون واجهات برمجة التطبيقات غير المتزامنة غير حاصرة وأن تعرض النتائج بسرعة بعد بدء طلب العملية. يجب أن يكون من الآمن دائمًا طلب بيانات من واجهة برمجة تطبيقات غير متزامنة في أي وقت، ويجب ألا يؤدي طلب البيانات من واجهة برمجة تطبيقات غير متزامنة إلى حدوث لقطات متقطعة أو خطأ ANR.
يمكن أن يتم تشغيل العديد من العمليات وإشارات مراحل النشاط حسب الطلب من خلال المنصة أو المكتبات، ومن غير العملي أن يتوقّع المطوّر أن يكون على دراية شاملة بجميع المواقع المحتملة التي يمكن أن يتم استدعاء الرمز البرمجي منها. على سبيل المثال، يمكن إضافة Fragment
إلى FragmentManager
في معاملة متزامنة استجابةً لقياس View
وتنسيقه عندما يجب ملء محتوى التطبيق لملء المساحة المتاحة (مثل RecyclerView
). قد تنفّذ LifecycleObserver
التي تستجيب لدالّة ردّ الاتصال onStart
لدورة الحياة الخاصة بهذه الأجزاء عمليات بدء التشغيل لمرة واحدة بشكل معقول هنا، وقد يكون ذلك في مسار الرمز البرمجي المهم لإنتاج إطار رسوم متحركة خالٍ من التشويش. يجب أن يكون المطوِّر واثقًا دائمًا من أنّ استدعاء أي واجهة برمجة تطبيقات غير متزامنة استجابةً لأنواع عمليات معاودة الاتصال هذه في دورة الحياة لن يكون سببًا في حدوث إطار متقطّع.
ويعني ذلك أنّ العمل الذي تنفّذه واجهة برمجة التطبيقات غير المتزامنة قبل عرض النتيجة يجب أن يكون بسيطًا جدًا، مثل إنشاء سجلّ للطلب ودالة رد النداء المرتبطة به وتسجيله في محرك التنفيذ الذي ينفّذ العمل على أقصى تقدير. إذا كان تسجيل عملية غير متزامنة يتطلّب الاتصال بين العمليات (IPC)، يجب أن تتّخذ عملية تنفيذ واجهة برمجة التطبيقات أي إجراءات ضرورية لتلبية توقّعات المطوّرين هذه. قد يشمل ذلك واحدًا أو أكثر مما يلي:
- تنفيذ عملية اتصال بين العمليات (IPC) الأساسية كمكالمة رابطة أحادية الاتجاه
- إجراء مكالمة ثنائية الاتجاه إلى خادم النظام حيث لا يتطلّب إكمال التسجيل الحصول على قفل شديد التنازع
- نشر الطلب في سلسلة محادثات عاملة ضمن عملية التطبيق لتنفيذ تسجيل حظر عبر عملية الاتصال بين العمليات (IPC)
يجب أن تعرض واجهات برمجة التطبيقات غير المتزامنة قيمة فارغة وأن تعرض خطأً فقط في حال توفُّر وسيطات غير صالحة.
يجب أن تعرض واجهات برمجة التطبيقات غير المتزامنة جميع نتائج العملية المطلوبة في دالة الرجوع المقدَّمة. ويتيح ذلك للمطوّر تنفيذ مسار رمز واحد للنجاح ومعالجة الأخطاء.
قد تتحقّق واجهات برمجة التطبيقات غير المتزامنة من وسيطات القيمة الفارغة وتطرح NullPointerException
، أو تتحقّق من أنّ الوسيطات المقدَّمة تقع ضمن نطاق صالح وتطرح IllegalArgumentException
. على سبيل المثال، بالنسبة إلى دالة تقبل float
في النطاق من 0
إلى 1f
، قد تتحقّق الدالة من أنّ المَعلمة ضِمن هذا النطاق وتعرض IllegalArgumentException
إذا كانت خارج النطاق، أو قد يتم التحقّق من String
قصير للتأكّد من مطابقته لتنسيق صالح مثل الأرقام والحروف الأبجدية فقط. (تذكَّر أنّه يجب ألا يثق خادم النظام في عملية التطبيق أبدًا. يجب أن تكرّر أي خدمة تابعة لنظام التشغيل عمليات التحقّق هذه في الخدمة التابعة لنظام التشغيل نفسها.)
يجب الإبلاغ عن جميع الأخطاء الأخرى إلى دالة الرجوع المقدَّمة. ويشمل ذلك، على سبيل المثال لا الحصر، ما يلي:
- خطأ نهائي في العملية المطلوبة
- استثناءات الأمان المتعلقة بعدم توفّر التفويض أو الأذونات المطلوبة لإكمال العملية
- تجاوز الحصة المحدّدة لإجراء العملية
- لا تكون عملية التطبيق "في المقدّمة" بشكل كافٍ لتنفيذ العملية
- تم فصل الأجهزة المطلوبة
- أعطال الشبكة
- عملية استبعاد للقناة لمهلة معيّنة
- إيقاف عملية Binder نهائيًا أو عدم توفّر عملية بعيدة
يجب أن توفّر واجهات برمجة التطبيقات غير المتزامنة آلية إلغاء.
يجب أن توفّر واجهات برمجة التطبيقات غير المتزامنة طريقة للإشارة إلى عملية قيد التشغيل بأنّ المتصل لم يعُد مهتمًا بالنتيجة. يجب أن تشير عملية الإلغاء هذه إلى أمرين:
يجب تحرير المراجع الثابتة لبرامج معالجة المكالمات التي يقدّمها المتصل
قد تحتوي عمليات الرجوع التي يتم توفيرها لواجهات برمجة التطبيقات غير المتزامنة على مراجع ثابتة إلى رسومات بيانية كبيرة للكائنات، ويمكن أن يؤدي العمل الجاري الذي يتضمّن مرجعًا ثابتًا إلى عملية الرجوع هذه إلى منع جمع البيانات غير الضرورية من رسومات بيانية الكائنات هذه. من خلال إصدار مراجع عمليات الاستدعاء هذه عند الإلغاء، قد تصبح رسومات العناصر هذه مؤهَّلة لجمع البيانات غير الضرورية بشكل أسرع بكثير مما لو كان مسموحًا بتنفيذ العمل حتى اكتماله.
قد يوقف محرك التنفيذ الذي ينفّذ العمل للمتصل هذا العمل
قد تتسبّب المهام التي يتم بدءها من خلال طلبات غير متزامنة من واجهة برمجة التطبيقات في تكلفة عالية من حيث استهلاك الطاقة أو موارد النظام الأخرى. تسمح واجهات برمجة التطبيقات التي تتيح للمتصلين الإشارة إلى عدم الحاجة إلى هذا العمل بإيقافه قبل أن يستهلك المزيد من موارد النظام.
اعتبارات خاصة للتطبيقات المخزَّنة مؤقتًا أو المجمَّدة
عند تصميم واجهات برمجة تطبيقات غير متزامنة تنشأ فيها عمليات معاودة الاتصال في عملية نظام ويتم تسليمها إلى التطبيقات، يجب مراعاة ما يلي:
- العمليات ودورة حياة التطبيق: قد تكون عملية تطبيق المستلِم في حالة التخزين المؤقت.
- تجميد التطبيقات المخزّنة مؤقتًا: قد يتم تجميد عملية تطبيق المستلِم.
عندما تدخل عملية تطبيق في حالة التخزين المؤقت، يعني ذلك أنّها لا تستضيف بشكل نشط أي مكوّنات مرئية للمستخدمين، مثل الأنشطة والخدمات. يتم الاحتفاظ بالتطبيق في الذاكرة في حال أصبح مرئيًا للمستخدم مرة أخرى، ولكن في الوقت الحالي، يجب ألا ينفّذ أي مهام. في معظم الحالات، عليك إيقاف إرسال معاودة الاتصال الخاصة بالتطبيق مؤقتًا عندما يدخل التطبيق في حالة التخزين المؤقت، ثم استئنافها عندما يخرج التطبيق من حالة التخزين المؤقت، وذلك لتجنُّب إحداث أي عمليات في عمليات التطبيقات المخزَّنة مؤقتًا.
قد يتم أيضًا تجميد تطبيق مخزَّن مؤقتًا. عند تجميد تطبيق، لا يتلقّى أي وقت لوحدة المعالجة المركزية (CPU) ولا يمكنه تنفيذ أي مهام على الإطلاق. يتم تخزين أي عمليات استدعاء للدوال المعالجة المسجّلة في التطبيق مؤقتًا وتسليمها عند إلغاء تجميد التطبيق.
قد تكون المعاملات المخزّنة مؤقتًا في عمليات رد الاتصال للتطبيق قديمة عند إلغاء تجميد التطبيق ومعالجتها. المخزن المؤقت محدود، وفي حال تجاوز سعته، سيتعطّل تطبيق المستلِم. لتجنُّب إرهاق التطبيقات بالأحداث القديمة أو تجاوز سعة المخازن المؤقتة، لا ترسِل عمليات معاودة الاتصال بالتطبيقات أثناء تجميد عملياتها.
قيد المراجعة:
- عليك التفكير في إيقاف إرسال عمليات معاودة الاتصال في التطبيق مؤقتًا أثناء تخزين عملية التطبيق مؤقتًا.
- يجب إيقاف إرسال عمليات معاودة الاتصال من التطبيق مؤقتًا أثناء تجميد عملية التطبيق.
تتبُّع الحالة
لتتبُّع وقت دخول التطبيقات إلى حالة التخزين المؤقت أو الخروج منها، اتّبِع الخطوات التالية:
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()
إلا إذا لم يتم تجميد العملية.
تحفظ التطبيقات غالبًا التعديلات التي تلقّتها باستخدام عمليات معاودة الاتصال كلقطة للحالة الأخيرة. لنفترض أنّ هناك واجهة برمجة تطبيقات تتيح للتطبيقات تتبُّع النسبة المئوية المتبقية من شحن البطارية:
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));
في بعض الحالات، يمكنك تتبُّع القيمة الأخيرة التي تم تسليمها إلى التطبيق حتى لا يحتاج التطبيق إلى تلقّي إشعار بالقيمة نفسها بعد إلغاء تجميده.
يمكن التعبير عن الحالة كبيانات أكثر تعقيدًا. لنفترض أنّ هناك واجهة برمجة تطبيقات لإعلام التطبيقات بواجهات الشبكة:
interface NetworkListener {
void onAvailable(Network network);
void onLost(Network network);
void onChanged(Network network);
}
عند إيقاف الإشعارات مؤقتًا لتطبيق ما، عليك تذكُّر مجموعة الشبكات والحالات التي رآها التطبيق آخر مرة. عند الاستئناف، يُنصح بإبلاغ التطبيق بالشبكات القديمة التي تم فقدانها، والشبكات الجديدة التي أصبحت متاحة، والشبكات الحالية التي تغيّر حالتها، وذلك بهذا الترتيب.
عدم إرسال إشعار إلى التطبيق بشأن الشبكات التي أصبحت متاحة ثم تعذّر الوصول إليها أثناء إيقاف عمليات معاودة الاتصال مؤقتًا يجب ألا تتلقّى التطبيقات سجلّاً كاملاً للأحداث التي وقعت أثناء تجميدها، ويجب ألا تتضمّن مستندات واجهة برمجة التطبيقات وعودًا بتسليم تدفقات الأحداث بدون انقطاع خارج حالات مراحل النشاط الصريحة. في هذا المثال، إذا كان التطبيق بحاجة إلى مراقبة مدى توفّر الشبكة بشكل مستمر، يجب أن يظل في حالة دورة حياة تمنعه من أن يصبح مخزّنًا مؤقتًا أو مجمّدًا.
أثناء المراجعة، عليك دمج الأحداث التي وقعت بعد إيقاف الإشعارات مؤقتًا وقبل استئنافها، وتقديم أحدث حالة إلى عمليات رد الاتصال المسجّلة في التطبيق بإيجاز.
اعتبارات بشأن مستندات المطوّرين
قد يتأخّر تسليم الأحداث غير المتزامنة، إما لأنّ المرسِل أوقف التسليم مؤقتًا لمدة زمنية كما هو موضّح في القسم السابق، أو لأنّ تطبيق المستلِم لم يتلقَّ موارد كافية من الجهاز لمعالجة الحدث في الوقت المناسب.
ننصح المطوّرين بعدم وضع افتراضات بشأن الوقت الفاصل بين تلقّي التطبيق إشعارًا بحدث ووقت حدوث الحدث فعليًا.
توقّعات المطوّرين بشأن تعليق واجهات برمجة التطبيقات
يتوقّع المطوّرون الذين يعرفون التزامن المنظَّم في Kotlin السلوكيات التالية من أي واجهة برمجة تطبيقات معلّقة:
يجب أن تُكمل الدوال المعلقة جميع الأعمال المرتبطة بها قبل إرجاع القيمة أو عرض الخطأ
يتم عرض نتائج العمليات غير الحظرية كقيم عادية لعرض الدالة، ويتم الإبلاغ عن الأخطاء من خلال طرح استثناءات. (يعني هذا غالبًا أنّ مَعلمات معاودة الاتصال غير ضرورية).
يجب ألا تستدعي الدوال المعلقة مَعلمات رد الاتصال إلا في مكانها
يجب أن تكمل الدوال المعلقة جميع الأعمال المرتبطة بها قبل إرجاعها، لذلك يجب ألا تستدعي أبدًا دالة رد اتصال أو مَعلمة دالة أخرى أو تحتفظ بمرجع لها بعد أن ترجع الدالة المعلقة.
يجب أن تحافظ الدوال المعلقة التي تقبل مَعلمات دالة رد الاتصال على السياق ما لم يتم توثيق خلاف ذلك
يؤدي استدعاء دالة في دالة معلّقة إلى تشغيلها في
CoroutineContext
للمستدعي. بما أنّ دوال التعليق يجب أن تُكمل جميع المهام المرتبطة بها قبل أن تعرض قيمة أو تُطلق استثناءً، ويجب ألا تستدعي مَعلمات معاودة الاتصال إلا في مكانها، فإنّ التوقّع التلقائي هو أنّ أيّ عمليات معاودة اتصال من هذا النوع يتم تنفيذها أيضًا على CoroutineContext
الذي يتم استدعاؤه باستخدام أداة الإرسال المرتبطة به. إذا كان الغرض من واجهة برمجة التطبيقات هو تنفيذ دالة ردّ خارج CoroutineContext
، يجب توثيق هذا السلوك بوضوح.
يجب أن تتوافق دوال التعليق مع إلغاء مهمة kotlinx.coroutines
يجب أن تتوافق أي وظيفة تعليق مؤقت مع إلغاء المهمة على النحو المحدّد في kotlinx.coroutines
. إذا تم إلغاء مهمة الاتصال بعملية قيد التقدم، يجب أن تستأنف الدالة مع CancellationException
في أقرب وقت ممكن حتى يتمكن المتصل من التنظيف والمتابعة في أقرب وقت ممكن. تتولّى suspendCancellableCoroutine
وغيرها من واجهات برمجة التطبيقات المعلقة التي توفّرها 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
بدلاً من أن تكون كسمات فئة تتطلّب مزامنة إضافية.
يجب أن توفّر الفئات التي تدير إجراءات روتينية متزامنة طريقتَي إغلاق وإلغاء
يجب أن توفّر الفئات التي تبدأ إجراءات روتينية مشتركة كتفاصيل تنفيذية طريقة لإيقاف المهام المتزامنة الجارية بشكل سليم حتى لا تتسرّب المهام المتزامنة غير المتحكَّم فيها إلى نطاق رئيسي. عادةً ما يتم ذلك من خلال إنشاء عنصر فرعي 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
غلافًا غير ضروري ومضلّلاً، وقد يتم إنشاؤه في بعض حالات الاستخدام فقط لتمريره كمَعلمة في الدالة الإنشائية، ثم يتم تجاهله:
// Don't do this; just pass the context
val myObject = MyClass(CoroutineScope(parentScope.`CoroutineContext` + Dispatchers.IO))
يتم ضبط المَعلمات CoroutineContext تلقائيًا على EmptyCoroutineContext
عندما تظهر المَعلمة الاختيارية CoroutineContext
في مساحة عرض واجهة برمجة التطبيقات، يجب أن تكون القيمة التلقائية هي Empty`CoroutineContext`
. يتيح ذلك إمكانية دمج سلوكيات واجهة برمجة التطبيقات بشكل أفضل، إذ يتم التعامل مع قيمة 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)
// ...
}