محتويات الدورة
Kotlin
أسس اللغة والحالة الصريحة
السلوك القابل للاستخدام والحدود المنمطة
التزامن والتكامل والتصميم المترابط

Kotlin / التزامن والتكامل والتصميم المترابط / الدرس 14

حفظ إشارة الإلغاء وتصميم ملكية الإخفاق

استخدم الإلغاء التعاوني وتنظيف finally وحدود المهلة والإشراف، دون ابتلاع الإشارات المتعلقة بعمر العمل.

وقت القراءة التقريبي: 5 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • اشرح لماذا يكون الإلغاء طلبا يجب أن تلاحظه الشيفرة، لا إيقافا قسريا لها.
  • ميّز نطاقات الكوروتينات التي تفشل أجزاؤها معا من إشراف يسمح باستقلال إخفاق الأبناء.

لا يوقف الإلغاء أي شيفرة بالقوة

إلغاء الكوروتين تعاوني. تفحص عمليات التعليق المكتبية كثيرا حالة الإلغاء، بينما قد تستمر حلقة حساب طويلة أو استدعاء حاجب ما لم يستخدم آلية إلغاء مدعومة. تفحص ensureActive النطاق أو السياق الحالي وترمي عند انطباق الإلغاء؛ وتستطيع yield أيضا إتاحة فرصة لعمل آخر كي يُنفّذ. اختر تواتر الفحص وفقا لطبيعة العمل ومطلب الاستجابة، بدلا من وضع فترة سحرية عامة. ولا يتراجع طلب الإلغاء عن فعل خارجي سبق تثبيته، لذلك تحتاج العمليات المعدّلة للحالة إلى تصميم مستقل لنتائجها واستعادتها.

انتظر اكتمال الإلغاء قبل افتراض انتهاء التنظيف

تطلب cancel الإلغاء، بينما تنتظر cancelAndJoin اكتمال الكوروتين أيضا. يبدأ المثال الكوروتين الابن باستخدام UNDISPATCHED، فتحدث الرسالة الأولى ثم التعليق قبل أن يلغيه المستدعي. تطبع finally علامة تنظيف، ولا يطبع الأب رسالته إلا بعد انتهاء الانتظار. لا يوجد هنا سباق يعتمد على مدة زمنية أو مورد خارجي. يجب أن يكون التنظيف الحقيقي محدودا ويحافظ على الإلغاء؛ وقد يحتاج التنظيف المعلق داخل سياق ملغى معالجة ضيقة بعناية حيث تُدعم. لا تستخدم مناطق واسعة غير قابلة للإلغاء لإبقاء العمل التجاري العادي حيا بعد انتهاء مالكه.

kotlin ترتيب إلغاء حتمي في المثال
import kotlinx.coroutines.*

fun main() = runBlocking {
    val child = launch(start = CoroutineStart.UNDISPATCHED) {
        try {
            println("started")
            awaitCancellation()
        } finally {
            println("cleanup")
        }
    }
    child.cancelAndJoin()
    println("cancelled and joined")
}
الناتج المتوقع
started
cleanup
cancelled and joined

الأسطر المتوقعة هي started ثم cleanup ثم cancelled and joined. تلزم kotlinx.coroutines متوافقة، ولم يُترجم البرنامج أو يُنفّذ. علامة الطباعة ليست دليلا على إغلاق مورد حقيقي.

لا تحوّل CancellationException إلى نجاح عادي

تمثّل CancellationException إشارة دورة حياة يستخدمها إلغاء الكوروتينات. قد تلتقطها catch واسعة من نوع Exception، وقد تفعل ذلك أيضا أدوات تلتقط استثناءات اعتباطية. عند ترجمة الإخفاقات العادية، أعد رمي الإلغاء صراحة قبل معالجة بقية الاستثناءات. وإلا فقد ينتظر المستدعي بلا حاجة أو يتلقى نجاحا مختلقا بعد طلب الإلغاء. يجب أن تحرّر finally الموارد المملوكة وتحافظ على تدفق التحكم الأصلي. والتقاط Throwable أوسع، وقد يخفي إخفاقات لا يستطيع التطبيق التعافي منها. اجعل الحد يعالج فقط الإخفاقات التي يفهمها فعلا.

تلغي المهلة نطاقا ولا تعكس آثاره في العالم

ترمي withTimeout الاستثناء TimeoutCancellationException عند انطباق المهلة، بينما تتيح withTimeoutOrNull نتيجة خالية لحالة انتهاء المهلة. وتظل الكتلة مطالبة بالتعاون مع الإلغاء. تمنع مهلة صفرية أو سالبة تشغيل الكتلة فورا، ولذلك يكون المثال حتميا بلا نوم. قد تتسابق المهلة مع إعادة مورد؛ فاقتنه واستخدمه وأغلقه داخل نطاق ملكية مناسب. لا تدّع أن حجزا بعيدا انتهت مهلته لم يحدث، ولا تعده بهوية جديدة قبل معرفة نتيجته التجارية.

kotlin حد مهلة غير موجبة
import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = withTimeoutOrNull(0L) {
        "block result"
    }
    println(result ?: "timed out before block")
}
الناتج المتوقع
timed out before block

المخرج المتوقع timed out before block. لا تُنفّذ الكتلة عند المهلة الصفرية المعلنة. هذا توقع مبني على عقد الواجهة، وليس تجربة توقيت منفذة.

أشرف على العمل المستقل دون تجاهل إخفاقاته

تناسب coroutineScope المهام الفرعية التي يجب أن تنجح معا. تستطيع supervisorScope جعل الكوروتينات الأشقاء مستقلة عند فشل أحدها، لكنها لا تستهلك كل استثناء تلقائيا. فقد يظل إخفاق launch تحت الإشراف غير معالج، وتحتاج async تحت الإشراف إلى ملاحظة نتيجتها أو إخفاقها. وإذا فشلت كتلة supervisorScope نفسها، فإنها تلغي الكوروتينات الأبناء. استخدم الإشراف عندما يسمح المنتج حقا بنتائج جزئية، وحدد تمثيل كل إخفاق. ليس الإشراف مفتاحا عاما يجعل التطبيق مرنا، ولا يغني عن إلغاء العمر وحدود الموارد.

الأخطاء الشائعة وإصلاحها

يُلتقط الإلغاء ويعاد بوصفه نجاحا فارغا.

لماذا يحدث؟
ابتلعت آلية واسعة لترجمة الاستثناءات إشارة دورة الحياة.
كيف تصلحه؟
أعد رمي CancellationException، وعالج الإخفاقات العادية المقصودة فقط.
كيف تتجنب تكراره؟
ضمّن حالات الإلغاء في مراجعة catch والأدوات التي تحول الاستثناءات إلى نتائج.

تتجاهل حلقة كثيفة الحساب طلب الإلغاء.

لماذا يحدث؟
لا تتعلق الحلقة ولا تفحص السياق النشط.
كيف تصلحه؟
أضف فحوصا تعاونية عند حدود عمل مبررة.
كيف تتجنب تكراره؟
حدّد مطلب الاستجابة وحدود الموارد للحسابات الطويلة.

يترك الإشراف إخفاق كوروتين ابن بلا معالجة.

لماذا يحدث؟
اختلط استقلال الإخفاق باستهلاك الأخطاء تلقائيا.
كيف تصلحه؟
راقب نتائج async وعالج إخفاقات launch وفقا لسياسة ذات مالك واضح.
كيف تتجنب تكراره؟
حدّد معنى النتائج الجزئية قبل إضافة الإشراف.

اختبر فهمك

1. لماذا لا يوثَق في استدعاء cancel ثم قراءة حالة التنظيف مباشرة؟

اعرض الحل المشروح

تطلب cancel الإلغاء، لكنها لا تضمن أن الكوروتين الابن أكمل finally في تلك اللحظة. استخدم انتظار اكتمال مناسبًا عبر join عندما يحتاج المالك إلى الانتظار، وصمّم وضوح حالة التنظيف وملكية الموارد صراحة. وحتى اكتمال كوروتين بعد الانتظار لا يثبت أن نظاما خارجيا عكس أثرا سبق تثبيته.

2. متى ينبغي لجزأين من تقرير استخدام supervisorScope بدلا من سلوك يفشل فيه الجميع معا؟

اعرض الحل المشروح

عندما يسمح المنتج ببقاء أحد الجزأين مفيدا إذا فشل الآخر، ويمثل التطبيق الجزء الغائب وخطأه صراحة. راقب نتيجة كل جزء، وحافظ على إلغاء التقرير ككل، وحدّ استخدام الموارد. إذا كان الجزآن مطلوبين للصحة، فقد ينتج استقلال بقائهما نجاحا ناقصا ومضللا.

أهم ما تعلمته

  • الإلغاء تعاوني، ويجب أن تظل إشارته ظاهرة عبر معالجة الأخطاء.
  • يغير الإشراف علاقات الإخفاق، ولذلك يحتاج سياسة منتج صريحة.

المراجع الرسمية