تطلب واجهات برمجة التطبيقات غير المحظورة إجراء عمل معيّن، ثمّ تعيد التحكّم إلى سلسلت الرسائل المُستخدِمة في الطلب حتى تتمكّن من تنفيذ عمل آخر قبل اكتمال العملية المطلوبة. تكون واجهات برمجة التطبيقات هذه مفيدة في الحالات التي قد يكون فيها العمل المطلوب جاريًا أو قد يتطلّب الانتظار حتى اكتمال عمليات الإدخال/الإخراج أو تنسيق البيانات بين العمليات (IPC)، أو توفّر موارد النظام التي يُجري عليها عدد كبير من العمليات عمليات طلب، أو إدخال المستخدم قبل أن تتمكّن من متابعة العمل. توفّر واجهات برمجة التطبيقات المصمّمة جيدًا بشكل خاص طريقة لإلغاء العملية الجارية وإيقاف تنفيذ العمل نيابةً عن المُتصل الأصلي، ما يحافظ على حالة النظام وعمر البطارية عندما لا تكون العملية مطلوبة.
واجهات برمجة التطبيقات غير المتزامنة هي إحدى طرق تحقيق السلوك غير المحظور. تقبل واجهات Async APIs بعض أشكال المتابعة أو الاستدعاء التي يتم إرسال إشعارات إليها عند اكتمال العملية أو عند حدوث أحداث أخرى أثناء سير العملية.
هناك سببان رئيسيان لكتابة واجهة برمجة تطبيقات غير متزامنة:
- تنفيذ عمليات متعدّدة في الوقت نفسه، حيث يجب بدؤه عملية N قبل اكتمال العملية N-1
- تجنُّب حظر سلسلة محادثات طلب الاتصال إلى أن تكتمل إحدى العمليات
تشجّع Kotlin بشدة استخدام المعالجة المتزامنة منظَّمة، وهي سلسلة من المبادئ وواجهات برمجة التطبيقات المستندة إلى وظائف التعليق التي تفصل بين تنفيذ الرمز البرمجي المتزامن وغير المتزامن وسلوك حظر الخيط. دوال التعليق غير محظورة ومتزامنة.
تعليق الدوال:
- لا تحظر سلسلة المحادثات التي تتصل بها، بل استخدِم سلسلة التنفيذ بدلاً من ذلك كأحد تفاصيل التنفيذ أثناء انتظار نتائج العمليات التي يتم تنفيذها في مكان آخر.
- أن يتم تنفيذها بشكل متزامن وألا تتطلّب من المُرسِل لطلب بيانات من واجهة برمجة تطبيقات غير مُشغِّلة للعمليات مواصلة التنفيذ بشكل متزامن مع العمل غير المُشغِّل للعمليات الذي بدأه طلب بيانات من واجهة برمجة التطبيقات
توضِّح هذه الصفحة الحد الأدنى من التوقعات التي يمكن للمطوّرين تلبيتها بأمان عند العمل مع واجهات برمجة التطبيقات غير المحظورة وغير المتزامنة، وتليها سلسلة من الإجراءات لإنشاء واجهات برمجة تطبيقات تستوفي هذه التوقعات بلغتَي Kotlin أو Java أو في نظام Android الأساسي أو مكتبات Jetpack. في حال الشك، يمكنك اعتبار متطلبات المطوّرين متطلبات لأي مساحة عرض جديدة لواجهة برمجة التطبيقات.
توقعات المطوّرين بشأن واجهات برمجة التطبيقات غير المتزامنة
تم وضع التوقّعات التالية من وجهة نظر واجهات برمجة التطبيقات التي لا تؤدي إلى تعليق الحسابات ما لم يُذكر خلاف ذلك.
تكون واجهات برمجة التطبيقات التي تقبل عمليات تسجيل المكالمات غير متزامنة عادةً.
إذا كانت واجهة برمجة التطبيقات تقبل طلب استدعاء لم يتم توثيقه ليتم استدعاؤه في مكانه فقط (أي أنّه لا يتم استدعاؤه إلا من خلال سلسلة المهام التي تُجري الطلب قبل أن تُرجع طلب البيانات من واجهة برمجة التطبيقات)، يُفترض أن تكون واجهة برمجة التطبيقات غير متزامنة، ويجب أن تستوفي واجهة برمجة التطبيقات جميع التوقعات الأخرى الموثَّقة في الأقسام التالية.
من الأمثلة على دالة الاستدعاء التي يتمّ استدعاؤها على الفور فقط دالة تصفية أو ربط من الدرجة الأعلى تستدعي أداة ربط أو تعبيرًا منطقيًا على كل عنصر في مجموعة قبل عرض النتيجة.
يجب أن تُعرِض واجهات برمجة التطبيقات غير المتزامنة البيانات في أسرع وقت ممكن.
يتوقّع المطوّرون أن تكون واجهات برمجة التطبيقات غير المتزامنة غير حظر وأن تُعرِض النتيجة بسرعة بعد بدء طلب العملية. من المفترض أن يكون من الآمن دائمًا طلب بيانات من واجهة برمجة التطبيقات التي تعمل بشكل غير متزامن في أي وقت، ولا يُفترض أن يؤدي طلب البيانات من واجهة برمجة التطبيقات التي تعمل بشكل غير متزامن إلى حدوث تقطُّع في عرض اللقطات أو ظهور أخطاء ANR.
يمكن أن تبدأ المنصة أو المكتبات العديد من العمليات وإشارات دورة الحياة عند الطلب، ومن غير المستدام توقّع أن يكون لدى المطوّر معرفة شاملة بكل المواقع الإلكترونية المحتملة التي يتم فيها استدعاء رمزه البرمجي. على سبيل المثال، يمكن إضافة Fragment
إلى FragmentManager
في معاملة متزامنة استجابةً
لقياس View
وتنسيقه عندما يجب تعبئة محتوى التطبيق لملء
المساحة المتاحة (مثل RecyclerView
). يمكن أن يؤدي LifecycleObserver
الذي يستجيب
لردّ اتصال onStart
لدورة حياة هذا المقتطف إلى تنفيذ عمليات بدء
لمرة واحدة بشكل معقول هنا، وقد يكون ذلك في مسار رمز برمجي مهم لإنشاء
إطار من الصور المتحركة بدون انقطاع. يجب أن يثق المطوّر دائمًا بأنّه لن يؤدي
استدعاء أي واجهة برمجة تطبيقات غير متزامنة استجابةً لهذه الأنواع من عمليات الاستدعاء المرجعية لدورة الحياة
إلى حدوث تقطُّع في عرض اللقطات.
ويشير ذلك إلى أنّ العمل الذي تؤديه واجهة برمجة التطبيقات غير المتزامنة قبل عرض النتيجة يجب أن يكون خفيفًا جدًا، على الأكثر إنشاء سجلّ للطلب وإجراء ردّ الاتصال المرتبط به وتسجيله في محرّك التنفيذ الذي ينفّذ العمل. إذا كان تسجيل عملية غير متزامنة يتطلب تنسيق IPC، يجب أن يتّخذ تنفيذ واجهة برمجة التطبيقات أيّ تدابير ضرورية لاستيفاء توقعات المطوّرين هذه. قد يشمل ذلك واحدًا أو أكثر مما يلي:
- تنفيذ واجهة برمجة تطبيقات أساسية للتواصل بين العمليات كطلب رابط أحادي الاتجاه
- إجراء مكالمة بين اثنين من تطبيقات الربط في خادم النظام حيث لا يتطلّب إكمال تسجيل العميل الحصول على قفل يُحتجّب عليه بشكل كبير
- إرسال الطلب إلى سلسلة مهام عامل في عملية التطبيق لتنفيذ تسجيل ملف شخصي محظور من خلال واجهة برمجة التطبيقات
يجب أن تعرض واجهات برمجة التطبيقات غير المتزامنة قيمة فارغة ولا تعرِض سوى وسيطات غير صالحة.
يجب أن تُبلغ واجهات برمجة التطبيقات غير المتزامنة عن جميع نتائج العملية المطلوبة إلى الدالة callback المقدَّمة. ويسمح ذلك للمطوّر بتنفيذ مسار رمز برمجي واحد للنجاح ومعالجة الأخطاء.
قد تتحقّق واجهات برمجة التطبيقات غير المتزامنة من الوسيطات بحثًا عن قيم فارغة وتُعرِض NullPointerException
، أو
تتحقّق من أنّ الوسيطات المقدَّمة ضمن نطاق صالح وتُعرِض
IllegalArgumentException
. على سبيل المثال، بالنسبة إلى دالة تقبل float
في النطاق من 0
إلى 1f
، قد تتحقّق الدالة من أنّ المَعلمة ضمن
هذا النطاق وتُعرِض IllegalArgumentException
إذا كانت خارج النطاق، أو قد يتم التحقّق من
String
قصير للتأكّد من توافقه مع تنسيق صالح، مثل
الأحرف الأبجدية الرقمية فقط. (تذكَّر أنّ خادم النظام يجب ألا يثق أبدًا في عملية
التطبيق. من المفترض أن تكرّر أي خدمة نظام عمليات التحقّق هذه في خدمة النظام
نفسها.)
يجب الإبلاغ عن جميع الأخطاء الأخرى إلى دالة الاستدعاء المقدَّمة. ويشمل ذلك، على سبيل المثال لا الحصر، ما يلي:
- خطأ نهائي في العملية المطلوبة
- استثناءات الأمان لعدم توفّر التفويض أو الأذونات المطلوبة لإكمال العملية
- تجاوز الحصة المحدّدة لتنفيذ العملية
- عملية التطبيق ليست "في المقدّمة" بشكل كافٍ لتنفيذ العملية
- تم فصل الجهاز المطلوب
- حالات تعذُّر الاتصال بالشبكة
- عملية استبعاد للقناة لمهلة معيّنة
- إيقاف عملية الربط نهائيًا أو عدم توفّر عملية عن بُعد
يجب أن توفّر واجهات برمجة التطبيقات غير المتزامنة آلية لإلغاء الطلبات.
يجب أن توفّر واجهات برمجة التطبيقات غير المتزامنة طريقة للإشارة إلى عملية جارية بأنّه لم يعُد المُرسِل مهتمًا بالنتيجة. من المفترض أن تشير عملية الإلغاء هذه إلى أمرين:
يجب إزالة الإشارات الثابتة إلى عمليات معاودة الاتصال التي قدّمها المتصل.
قد تحتوي وظائف الاستدعاء المقدَّمة لواجهات برمجة التطبيقات غير المتزامنة على إشارات ثابتة إلى رسومات تخطيطية كبيرة للعناصر، ويمكن أن يؤدي العمل الجاري الذي يحتفظ بإحالة ثابتة إلى وظيفة الاستدعاء هذه إلى منع جمع القمامة من رسومات تخطيطية للعناصر هذه. من خلال إزالة مراجع callback هذه عند الإلغاء، قد تصبح مخطّطات تمثيل الكائنات هذه مؤهّلة لجمع القمامة في وقت أقرب بكثير مما لو تم السماح بتنفيذ العمل إلى نهايته.
قد يوقف محرّك التنفيذ الذي ينفِّذ عملًا للمستخدم هذا العمل.
قد تؤدي المهام التي تبدأ من خلال طلبات البيانات غير المتزامنة من واجهة برمجة التطبيقات إلى استهلاك طاقة أو موارد نظام أخرى بتكلفة عالية. تسمح واجهات برمجة التطبيقات التي تتيح للمتصلين الإشارة إلى أنّه لم يعد هناك حاجة إلى هذا العمل بإيقاف هذا العمل قبل أن يستهلك المزيد من موارد النظام.
اعتبارات خاصة بالتطبيقات المخزَّنة مؤقتًا أو المجمّدة
عند تصميم واجهات برمجة التطبيقات غير المتزامنة التي تأتي منها عمليات الاستدعاء في عملية نظام ويتم تسليمها إلى التطبيقات، ضَع في الاعتبار ما يلي:
- العمليات ودورة حياة التطبيق: قد تكون عملية التطبيق المستلِم في حالة التخزين المؤقت.
- مُجمِّد التطبيقات المخزّنة مؤقتًا: قد يتم تجميد عملية التطبيق المستلِم.
عندما تدخل عملية التطبيق في الحالة المخزّنة مؤقتًا، يعني ذلك أنّها لا تُستخدَم بشكلٍ نشط لاستضافة أي مكوّنات تظهر للمستخدم، مثل الأنشطة والخدمات. يتم الاحتفاظ بالتطبيق في الذاكرة في حال أصبح مرئيًا للمستخدم مرة أخرى، ولكن في الوقت الحالي، لا يجب أن يعمل. في معظم الحالات، يجب إيقاف إرسال طلبات إعادة الاتصال بالتطبيق مؤقتًا عندما يدخل هذا التطبيق في الحالة المخزّنة مؤقتًا واستئناف إرسالها عندما يخرج التطبيق من الحالة المخزّنة مؤقتًا، وذلك لتجنّب بدء عمل في عمليات التطبيق المخزّنة مؤقتًا.
وقد يتم أيضًا تجميد التطبيق المخزَّن مؤقتًا. عندما يتم تجميد تطبيق، لا يحصل على أي وقت معالجة وظرفيًا ولا يمكنه تنفيذ أي عمل على الإطلاق. يتم تخزين أي طلبات للوصول إلى وظائف 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()
إلا إذا لم تكن العملية مُجمّدة.
غالبًا ما تحفظ التطبيقات التعديلات التي تلقّتها باستخدام طلبات إعادة الاتصال كلقطة شاشة لحالة التطبيق الأخيرة. لنفترض أنّ هناك واجهة برمجة تطبيقات افتراضية تتيح للتطبيقات مراقبة النسبة المئوية المتبقية من البطارية:
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 السلوكَين التاليَين من أي واجهة برمجة تطبيقات تُعلّق العمليات:
يجب أن تُكمِل وظائف التعليق جميع الأعمال المرتبطة بها قبل عرض القيمة أو طرح استثناء.
يتم عرض نتائج العمليات غير المحظورة كقيم عرض الدالة العادية، ويتم الإبلاغ عن الأخطاء من خلال طرح استثناءات. (يعني ذلك غالبًا أنّ مَعلمات callback غير ضرورية).
يجب أن تستدعي وظائف التعليق مَعلمات ردّ الاتصال في مكانها فقط.
يجب أن تُكمِل دوالّ التعليق دائمًا كلّ الأعمال المرتبطة بها قبل عرض النتيجة، ولذلك يجب ألّا تستدعي أبدًا دالة ردّ اتصال مقدَّمة أو مَعلمة دالة أخرى أو تحتفظ بمرجع إليها بعد عرض نتيجة دالة التعليق.
يجب أن تحافظ دوال التعليق التي تقبل مَعلمات ردّ الاتصال على السياق ما لم يتم توثيق خلاف ذلك.
يؤدي استدعاء دالة في دالة تعليق إلى تشغيلها في
CoroutineContext
للمُستدعي. بما أنّ وظائف التعليق يجب أن تُكمِل كل المهام
المرتبطة بها قبل عرض القيمة أو طرح خطأ، ويجب أن تستدعي فقط مَعلمات callback
في مكانها، فإنّ التوقّع التلقائي هو أن يتم تنفيذ أيّ من عمليات الاستدعاء هذه
أيضًا في CoroutineContext
التي تُجري الاستدعاء باستخدام أداة الإرسال المرتبطة بها. إذا كان هدف واجهة برمجة التطبيقات هو تنفيذ طلب استدعاء خارج CoroutineContext
التي تُجري المكالمة، يجب توثيق هذا السلوك بوضوح.
يجب أن تتوافق دوال التعليق مع kotlinx.coroutines لإلغاء المهام.
يجب أن تتعاون أيّ وظيفة تعليق معروضة مع إلغاء المهام على النحو المحدّد
بواسطة kotlinx.coroutines
. في حال إلغاء مهمة الاتصال لعملية جارية، يجب أن تستأنف الوظيفة باستخدام CancellationException
في أقرب وقت ممكن حتى يتمكّن المُتصل من التنظيف ومواصلة العمل في أقرب وقت ممكن. يتم تلقائيًا التعامل مع هذه الطلبات من خلال suspendCancellableCoroutine
وواجهات برمجة التطبيقات الأخرى التي توفّرها kotlinx.coroutines
والتي تتيح تعليق الحساب. بشكل عام، ينبغي ألا تستخدم عمليات تنفيذ المكتبات suspendCoroutine
مباشرةً، لأنّها لا تتيح سلوك الإلغاء هذا تلقائيًا.
يجب أن توفّر وظائف التعليق التي تُجري عملًا حظرًا في الخلفية (غير السلسلة الرئيسية أو سلسلة واجهة المستخدم) طريقة لضبط أداة الإرسال المستخدَمة.
لا يُنصح بتعليق دالة حظر تمامًا ل تبديل سلاسل المحادثات.
يجب ألا يؤدي استدعاء وظيفة تعليق إلى إنشاء خيوط برمجية إضافية بدون السماح للمطوّر بتوفير خيط برمجي أو مجمع خيوط برمجية خاص به لتنفيذ هذا العمل. على سبيل المثال، قد يقبل أسلوب الإنشاء CoroutineContext
الذي يُستخدَم لتنفيذ عمل في الخلفية ل methods الصف.
يجب أن تعرض بدلاً من ذلك دالات التعليق التي تقبل مَعلمة 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
حزمة غير ضرورية ومضلِّلة قد يتم إنشاؤها في بعض حالات الاستخدام فقط لتمرير معلَمة لصانع الأشكال، ثم يتم تجاهلها:
// 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)
// ...
}