CoroutineExceptionHandler: المعالج العام للاستثناءات غير الملتقطة
ثبّت CoroutineExceptionHandler لتسجيل الاستثناءات غير المعالجة أو استردادها.
CoroutineExceptionHandler: المعالج العام للاستثناءات غير الملتقطة درس مجاني في Kotlin Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Kotlin Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Kotlin Academy 4 دروس في المجموع.
ما هو CoroutineExceptionHandler؟
CoroutineExceptionHandler هو عنصر في السياق يعالج الاستثناءات غير الملتقطة الصادرة عن coroutines التي لا تحتوي على معالج catch. وهو بمثابة حل أخير.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { context, exception ->
println("Caught unhandled: ${exception.message}")
}
fun main() = runBlocking {
val scope = CoroutineScope(Dispatchers.Default + handler)
scope.launch { throw RuntimeException("oops") }
delay(100)
}لـ Root Coroutines فقط
يلتقط CoroutineExceptionHandler الاستثناءات الصادرة عن coroutines الجذرية فقط، أي التي أُطلقت مباشرةً ضمن نطاق. أما coroutines الفرعية فتنشر الاستثناءات إلى النطاق الأب، وليس إلى المعالج.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { _, e -> println("Handler: ${e.message}") }
fun main() = runBlocking {
// Root coroutine — handler fires:
CoroutineScope(handler).launch {
throw RuntimeException("root error")
}
delay(100)
// NOT handler (child of coroutineScope):
// launch { launch { throw RuntimeException() } }
}لا يكتم الاستثناء
يُستدعى المعالج بعد أن يكون الاستثناء قد ألغى coroutine بالفعل. ويُستخدم للتسجيل أو الإبلاغ عن الأعطال أو تنفيذ التنظيف، وليس لاستئناف التنفيذ.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { _, e ->
println("[CrashReport] ${e::class.simpleName}: ${e.message}")
// send to Crashlytics, Sentry, etc.
}
fun main() = runBlocking {
CoroutineScope(SupervisorJob() + handler).apply {
launch { throw IllegalStateException("state error") }
launch { delay(100); println("still alive") }
delay(200)
cancel()
}
}المعالج + SupervisorJob
يُعد الجمع بين SupervisorJob + CoroutineExceptionHandler النمط القياسي للنطاقات طويلة العمر: تفشل المهام الفرعية بشكل مستقل، وتُسجَّل حالات الفشل غير المعالجة.
import kotlinx.coroutines.*
class AppScope {
private val handler = CoroutineExceptionHandler { _, e ->
println("Uncaught: ${e.message}")
}
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
}
fun main() = runBlocking {
val app = AppScope()
app.scope.launch { throw RuntimeException("task failed") }
app.scope.launch { delay(100); println("other task ok") }
delay(200)
app.scope.cancel()
}المعالج مع async
بالنسبة إلى async، تُخزَّن الاستثناءات في Deferred وتُرمى عند استدعاء await(). ولا يُستدعى المعالج مع async ما لم تتم إعادة القيمة المؤجلة دون انتظارها.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { _, e -> println("Handler: ${e.message}") }
fun main() = runBlocking {
val scope = CoroutineScope(SupervisorJob() + handler)
val deferred = scope.async { throw RuntimeException("async error") }
try {
deferred.await() // exception thrown here
} catch (e: RuntimeException) {
println("Caught from await: ${e.message}")
}
delay(50)
scope.cancel()
}مقارنة مع Thread.UncaughtExceptionHandler
بخلاف UncaughtExceptionHandler في Java، يُعد المعالج في Kotlin جزءًا من سياق coroutine، ولا ينطبق إلا على coroutines الموجودة ضمن نطاق ذلك السياق.
import kotlinx.coroutines.*
// Java style (applies to threads):
Thread.setDefaultUncaughtExceptionHandler { t, e ->
println("Thread ${t.name} threw: ${e.message}")
}
// Kotlin coroutine style (applies to coroutines in scope):
val handler = CoroutineExceptionHandler { _, e ->
println("Coroutine threw: ${e.message}")
}
fun main() = runBlocking { println("Handlers target different concurrency models") }نمط التسجيل باستخدام MDC
في تطبيقات الخادم، يمكن للمعالج التقاط معلومات من سياق coroutine، مثل معرّفات الطلبات، لاستخدامها في التسجيل المنظم قبل تمريرها إلى إطار التسجيل.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { ctx, e ->
val jobName = ctx[CoroutineName]?.name ?: "unknown"
println("[${jobName}] ERROR: ${e.message}")
}
fun main() = runBlocking {
CoroutineScope(SupervisorJob() + handler).launch(CoroutineName("DataLoader")) {
throw RuntimeException("fetch failed")
}
delay(100)
}التكامل مع الإبلاغ عن الأعطال
استخدم المعالج لتمرير الاستثناءات غير الملتقطة إلى خدمات الإبلاغ عن الأعطال مثل Firebase Crashlytics أو Sentry.
import kotlinx.coroutines.*
object CrashReporter {
fun record(e: Throwable) = println("[Crashlytics] ${e.message}")
}
val handler = CoroutineExceptionHandler { _, e ->
if (e !is CancellationException) CrashReporter.record(e)
}
val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
fun main() = runBlocking {
appScope.launch { throw RuntimeException("unhandled in production") }
delay(100)
appScope.cancel()
}توريث المعالج
لا ترث coroutines الفرعية المعالج الموجود في سياق الأب تلقائيًا. يجب أن يكون المعالج موجودًا في سياق coroutine الجذرية حتى يُستدعى.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { _, e -> println("Handler: ${e.message}") }
fun main() = runBlocking {
// Handler only fires at root level:
CoroutineScope(SupervisorJob() + handler).launch {
// Child of root — exception propagates to root handler:
launch { throw RuntimeException("nested") }
}
delay(100)
}ملخص أفضل الممارسات
ثبّت دائمًا CoroutineExceptionHandler في النطاقات على مستوى التطبيق أو الميزة. سجّل كل استثناء لا يتعلق بالإلغاء. ولا تعتمد عليه للتحكم في التدفق؛ فهو مخصص للرصد فقط.
import kotlinx.coroutines.*
val globalHandler = CoroutineExceptionHandler { ctx, e ->
if (e !is CancellationException) {
println("[ERROR] ${ctx[CoroutineName]?.name}: ${e.message}")
}
}
val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + globalHandler)
fun main() = runBlocking {
appScope.launch(CoroutineName("Auth")) { throw RuntimeException("token expired") }
delay(100); appScope.cancel()
}تحقق سريع
مع أي نوع من coroutines لا يُستدعى CoroutineExceptionHandler تلقائيًا؟
مراجعة
يُعد CoroutineExceptionHandler مراقبًا يُستخدم كحل أخير للاستثناءات غير الملتقطة في coroutines الجذرية. ادمجه مع SupervisorJob للنطاقات طويلة العمر. واستخدمه للتسجيل والإبلاغ عن الأعطال، وليس للتحكم في التدفق.
الأسئلة الشائعة
هل درس «CoroutineExceptionHandler: المعالج العام للاستثناءات غير الملتقطة» مجاني؟
نعم — نص درس «CoroutineExceptionHandler: المعالج العام للاستثناءات غير الملتقطة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Kotlin Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Kotlin Academy 4 دروس في المجموع.
ماذا ستتعلم في «CoroutineExceptionHandler: المعالج العام للاستثناءات غير الملتقطة»؟
ثبّت CoroutineExceptionHandler لتسجيل الاستثناءات غير المعالجة أو استردادها. تتمرن على Kotlin Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Kotlin Academy؟
لا تُشترط خبرة سابقة. Kotlin Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «CoroutineExceptionHandler: المعالج العام للاستثناءات غير الملتقطة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Kotlin Academy هذا؟
نعم. كل درس في Kotlin Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- SupervisorJob مقابل Job: عزل حالات الفشل
- CoroutineExceptionHandler: المعالج العام للاستثناءات غير الملتقطة
- انتشار الاستثناءات مع async/await
- تصميم معماريات Coroutines مرنة