runBlocking — ما هو، جسر الحظر وكيف يعمل

المؤلف: IT Sectr نُشر: 2026-06-22 وقت القراءة: 7 دق

runBlocking — منشئ Coroutine في Kotlin يحجر الخيط الحالي حتى اكتمال الكوروتين الممرر. بالخلاف من launch و async، ليس دالة suspend ويمكن استدعاؤه من كود عادي (حاجز). وفقًا لـ وثائق JetBrains، 2024، يعمل runBlocking كجسر بين العالم المتزامن وغير المتزامن، مما يسمح بتشغيل الكوروتينات من دالة main والاختبارات.

النقاط الرئيسية

  • runBlocking — منشئ حاجز ينشئ CoroutineScope وينتظر اكتمال الكوروتين
  • حجز الخيط — runBlocking يحتجز الخيط الحالي حتى اكتمال الكوروتين وجميع الأبناء
  • نقاط الدخول — main()، اختبارات JUnit والجسر بين الكود الحاجز وغير المتزامن
  • ممنوع على الخيط الرئيسي لـ Android — استدعاء runBlocking في خيط واجهة المستخدم يسبب ANR
  • بدائل — lifecycleScope، viewModelScope، TestCoroutineDispatcher لـ Android

ما هو runBlocking؟

runBlocking هي دالة Kotlin تنشئ CoroutineScope جديداً وتشغل الكوروتين الممرر، مما يحجر الخيط الحالي حتى اكتماله بالكامل. بالخلاف من جميع منشئات Coroutine الأخرى، runBlocking ليست دالة suspend ويمكن استدعاؤها من كود متزامن عادي. تأخذ دالة runBlocking CoroutineContext وكتلة suspend، وتعيد نتيجة من نوع T.

kotlin
public fun <T> runBlocking(
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend CoroutineScope.() -> T
): T

runBlocking تشغل event-loop جديداً على الخيط الحالي. عندما تستدعي الكوروتين دالة suspend (مثل delay() أو await())، runBlocking تحجر الخيط وتنفذ كوروتينات أخرى مجدولة على نفس الخيط حتى تستؤنف المعلقة. هذا هو الحجز التعاوني — الخيط ليس خاملاً بل يعالج كوروتينات أخرى.

كيف يعمل runBlocking

تعتمد آلية runBlocking الداخلية على event-loop: عند استدعاء دالة suspend، توقف runBlocking تنفيذ الكتلة الحالية وتشغل كوروتينات أخرى من الطابور. عندما تكتمل دالة suspend، يستؤنف التنفيذ. يستمر هذا الدورة حتى تنتهي جميع الكوروتينات.

Event-loop تحت الغطاء

runBlocking تستخدم مجموعة خيوط أحادية خاصة بها لتنفيذ الكوروتينات. بخلاف Dispatchers.IO أو Default، runBlocking لا تبدل الخيوط — بل تعالج جميع الكوروتينات على الخيط الحالي، متداولة تنفيذها. هذا هو المنشئ الوحيد الذي يضمن التنفيذ على نفس الخيط.

kotlin
fun main() {
    val threadName = Thread.currentThread().getName()
    println("قبل runBlocking على $threadName")

    val result = runBlocking {
        println("داخل runBlocking على ${Thread.currentThread().getName()}")
        delay(500L)
        "Done"
    }

    println("بعد runBlocking: $result")
}

سيظهر الإخراج أن جميع الأوامر println الثلاثة تتنفذ على نفس الخيط. runBlocking لا تبدل الخيوط بل تنظم تعدد مهام تعاونياً داخل خيط واحد باستخدام event-loop.

متى استخدام runBlocking

runBlocking مبررة في ثلاثة سيناريوهات: نقطة الدخول main() في تطبيقات واجهة الأوامر، اختبارات الوحدة لدوال suspend والجسر — استدعاء كود suspend من مكتبات قائمة على callbacks أو حاجزة. في كود Android الإنتاجي، استخدامه على الخيط الرئيسي ممنوع بشكل قاطع.

السيناريوالإمكانيةالمخاطر
main() لتطبيق واجهة الأوامرنعملا يوجد — هي نقطة الدخول، الخيط لا يحجر واجهة المستخدم
اختبارات JUnitنعمأدنى — الاختبارات متزامنة بالتعريف
خيط واجهة المستخدم في AndroidلاANR، تأخر، تجميد الواجهة
Callback → Coroutineنعم، بحذرحجز مجموعة الخيوط في العمليات الطويلة

لاختبارات Android، استخدم kotlinx-coroutines-test مع TestDispatcher بدلاً من runBlocking. يوفر ذلك التحكم في الوقت، التنظيف التلقائي وعزل الاختبارات.

بدائل runBlocking

في معظم السيناريوهات، يمكن وينبغي استبدال runBlocking ببدائل غير متزامنة. لـ Android، هذه هي viewModelScope، lifecycleScope أو CoroutineScope مع الموزع المناسب. للاختبارات — TestCoroutineDispatcher و runTest.

kotlin
    // سيئ: runBlocking على الخيط الرئيسي لـ Android
runBlocking(Dispatchers.Main) {
    val result = networkApi.fetchData()
    textView.setText(result)
}

// جيد: lifecycleScope
lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
    textView.setText(result)
}

للاختبارات، البديل هو runTest من kotlinx-coroutines-test. ينشئ TestCoroutineScope بوقت افتراضي، مما يسمح باختبار التأخيرات دون انتظار حقيقي. هذا يسرع الاختبارات ويجعلها حتمية.

أمثلة استخدام runBlocking

أكثر السيناريوهات شيوعًا هو اختبار دوال suspend. runBlocking في الاختبارات تسمح بالانتظار المتزامن لنتيجة الكوروتين دون تغيير المعمارية. السيناريو الثاني هو مكتبات بواصلات برمجة API قائمة على callbacks، حيث تستدعى دوال suspend من سياق حاجز عبر runBlocking.

kotlin
// اختبار دالة suspend باستخدام runBlocking
class RepositoryTest {
    @Test
    fun `fetchUser returns correct data`() {
        val repository = UserRepository(FakeApi())

        val result = runBlocking {
            repository.fetchUser("123")
        }

        assertEquals("John", result.name)
        assertEquals("john@test.com", result.email)
    }
}

للجسر بين callbacks وعالم suspend، استخدم CompletableDeferred مع runBlocking بدلاً من callbacks — هذا يبسط سلاسل العمليات غير المتزامنة ويحسن قراءة الكود.

مخاطر الاستخدام الخاطئ

الاستخدام غير الصحيح لـ runBlocking هو أحد الأخطاء الشائعة عند الانتقال من نهج الحجز إلى الكوروتينات. المشاكل الرئيسية: الاستدعاء على الخيط الرئيسي لـ Android، تداخل runBlocking داخل بعضها البعض، استخدامها داخل دوال غير متزامنة وتشغيل عمليات طويلة عبر runBlocking.

  • ANR — runBlocking على الخيط الرئيسي لـ Android تحجر عرض واجهة المستخدم لأكثر من 5 ثوان
  • التسيب (إغلاق متبادل) — runBlocking متداخلة داخل كوروتين على نفس الخيط تؤدي إلى الحجز المتبادل
  • الخلط بين الموزعات — Dispatchers.Main داخل runBlocking في خيط خلفي ليس له Looper ويرمي استثناء
  • تسريب الذاكرة — runBlocking لا تلغى تلقائياً عند إتلاف Activity/Fragment

القاعدة الذهبية: runBlocking هي جسر، ليس بديلاً. استخدمها فقط لربط العوالم الحاجزة وغير الحاجزة. لجميع المهام الأخرى، استخدم launch، async أو lifecycleScope.

الأسئلة الشائعة

لماذا يحجر runBlocking الخيط بينما لا تفعل المنشئات الأخرى؟

runBlocking هي المنشئ الوحيد الذي ليس دالة suspend. تبدأ event-loop على الخيط الحالي ولا تعيد التحكم حتى تكتمل جميع الكوروتينات. launch و async تعيدان التحكم فوراً، منفذتين الكوروتين في الخلفية.

هل يمكن استخدام runBlocking فو ViewModel في Android؟

غير موصى به. ViewModel لديها viewModelScope مضمن يدير الكوروتينات تلقائياً ويلغيها عند الإتلاف. runBlocking في ViewModel تحجر الخيط ولا تستجيب لإلغاء دورة الحياة.

بماذا نستبدل runBlocking في اختبارات الوحدة؟

استخدم runTest من مكتبة kotlinx-coroutines-test. يوفر TestCoroutineScope مع التحكم في الوقت الافتراضي، الإلغاء التلقائي والتنفيذ الحتمي.

ما هو event-loop في runBlocking؟

Event-loop هو دورة معالجة الأحداث داخل runBlocking. عندما تتعلق كوروتين (مثل delay())، يتحول event-loop إلى تنفيذ كوروتينات أخرى جاهزة على نفس الخيط. هذا يخلق وهم تعدد المهام دون تبديل الخيوط.

ماذا يحدث عند استدعاء runBlocking داخل runBlocking؟

runBlocking المتداخلة على نفس الخيط تنشئ إغلاقاً متبادلاً — الكتلة الخارجية تنتظر الداخلية، ولكن الداخلية لا يمكنها البدء حتى تنتهي الخارجية. على خيوط مختلفة يسمح بها ولكن غير موصى به بشدة بسبب تعقيد التصحيح.

الملخص

  • runBlocking — منشئ Coroutine حاجز، جسر بين الكود الحاجز وغير المتزامن
  • Event-loop runBlocking تعالج الكوروتينات بشكل تعاوني على خيط واحد دون تبديل
  • السيناريوهات المسموحة — main()، اختبارات JUnit، جسر من مكتبات callbacks
  • السيناريوهات الممنوعة — خيط واجهة المستخدم في Android، استدعاءات متداخلة، عمليات طويلة
  • البدائل — lifecycleScope، viewModelScope، runTest للاختبارات
  • خطر ANR — runBlocking على الخيط الرئيسي تتسبب في تجميد التطبيق بعد 5 ثوان
  • لكود Android الإنتاجي، استخدم منشئات غير متزامنة — runBlocking غير مصممة لواجهة المستخدم

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا