Kotlin: شرح الكوروتينات في كوتلن

آخر تحديث: 2026-08-26

الكوروتينات هي جوهر البرمجة غير المتزامنة في كوتلن — دالة suspend تتيح لتشارلي كتابة عمليات غير متزامنة بصياغة متزامنة، بينما التزامن المهيكل يضمن عدم تسرب أي كوروتين.

1. ما ستتعلمه


2. قصة مهندس حقيقية

(1) نقطة الألم: جحيم الاستدعاءات وانفجار الخيوط

استخدم إصدار تشارلي من OrderProcessor في جافا CompletableFuture للمنطق غير المتزامن — 3 مستويات من الاستدعاءات المتداخلة كانت غير مقروءة بالفعل. استخدم الفريق 200 خيط للطلبات المتزامنة، مع استهلاك حمل تبديل سياق المعالج 40% من السعة.

(2) حل الكوروتينات

KOTLIN
// جافا: استدعاءات CompletableFuture المتداخلة
CompletableFuture<Order> future = fetchOrder(id)
    .thenCompose(order -> fetchCustomer(order.getCustomerId()))
    .thenApply(customer -> enrichOrder(order, customer))
    .exceptionally(ex -> handleError(ex));

// كوتلن: كود يبدو متسلسلاً، تنفيذ غير متزامن
suspend fun processOrder(id: String): Order {
    val order = fetchOrder(id)           // تعليق، لا حظر
    val customer = fetchCustomer(order.customerId)  // تعليق مرة أخرى
    return enrichOrder(order, customer)
}

دوال suspend تجعل الكود غير المتزامن يُقرأ كمتزامن — الكوروتينات تعلق بدلاً من حظر الخيوط عند الانتظار. خيط واحد يمكنه التعامل مع 100,000 كوروتين.


3. الكوروتينات مقابل الخيوط

(1) الاختلافات الأساسية

KOTLIN
// الخيط: خيط واحد لكل مهمة متزامنة (ثقيل)
// 100,000 خيط = OOM (كل خيط ~1 ميجابايت مكدس)

// الكوروتين: خيوط افتراضية خفيفة الوزن
// 100,000 كوروتين = لا مشكلة (كل كوروتين ~بضع مئات من البايت)

fun main() = runBlocking {
    repeat(100_000) {
        launch {  // 100 ألف كوروتين - لا مشكلة!
            delay(1_000)  // تعليق (لا حظر)
            println("Coroutine $it done")
        }
    }
}

(2) مقارنة الكوروتين مع الخيط

البُعد الخيط الكوروتين
تكلفة الإنشاء ~1 ميجابايت ذاكرة المكدس ~بضع مئات من البايت
تبديل السياق مستوى نواة نظام التشغيل (بطيء) وضع المستخدم (سريع)
العدد الأقصى آلاف مئات آلاف
الحظر يحظر الخيط بالكامل يعلق الكوروتين فقط
الإلغاء غير آمن (stop مهمل) إلغاء تعاوني (آمن)
الاستثناءات صعب النشر نشر مهيكل

(3) مخطط مبدأ الكوروتين

100%
sequenceDiagram
    participant T as Thread
    participant C1 as Coroutine 1
    participant C2 as Coroutine 2
    participant IO as IO Operation

    T->>C1: Resume
    C1->>IO: fetchOrder(id)
    Note over C1: تعليق - الخيط حر
    T->>C2: Resume (نفس الخيط!)
    C2->>IO: fetchCustomer(id)
    Note over C2: تعليق - الخيط حر
    IO-->>C1: نتيجة الطلب
    Note over C1: استئناف
    C1-->>T: متابعة المعالجة
    IO-->>C2: نتيجة العميل
    Note over C2: استئناف

4. دوال suspend

(1) المفاهيم الأساسية

KOTLIN
// كلمة suspend: هذه الدالة يمكنها تعليق الكوروتين
suspend fun fetchOrder(id: String): Order {
    delay(500)  // محاكاة استدعاء شبكة (غير مانع)
    return Order(id, 299.99, "CONFIRMED")
}

// دوال suspend يمكن استدعاؤها فقط من كوروتينات أو دوال suspend أخرى
suspend fun processOrder(id: String): Order {
    val order = fetchOrder(id)      // نقطة تعليق
    val customer = fetchCustomer(order.customerId)  // نقطة تعليق
    return order.copy(customer = customer)
}

(2) قواعد دوال suspend

القاعدة الوصف
قابلة للاستدعاء فقط من كوروتينات أو دوال suspend يفرضها المترجم
لا تحظر الخيوط تعلق الكوروتين، تحرر الخيط
يمكنها استدعاء دوال عادية الدوال العادية لا يمكنها استدعاء suspend
جوهرياً تحويل CPS المترجم يحول suspend إلى آلة حالة

5. الأعمدة الثلاثة للتزامن المهيكل

(1) CoroutineScope

KOTLIN
// CoroutineScope: يحدد عمر الكوروتينات
// جميع الكوروتينات المنطلقة في نطاق مرتبطة بعمره

// runBlocking: يحظر الخيط الحالي حتى تكتمل جميع الكوروتينات
runBlocking {
    launch { delay(1_000); println("Done") }
}

// coroutineScope: يعلق (لا يحظر) حتى تكتمل جميع الأبناء
suspend fun fetchAll() = coroutineScope {
    val order = async { fetchOrder("ORD-001") }
    val customer = async { fetchCustomer("CUST-001") }
    Pair(order.await(), customer.await())
}

// نطاق مخصص (مثلاً في صنف)
class OrderService {
    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    fun process(order: Order) {
        scope.launch { /* عمل غير متزامن */ }
    }

    fun shutdown() {
        scope.cancel()  // إلغاء جميع الكوروتينات الأبناء
    }
}

(2) Job — دورة حياة الكوروتين

KOTLIN
val job = launch {
    println("Working...")
    delay(1_000)
    println("Done")
}

// حالات Job: New -> Active -> Completing -> Completed
//                                    -> Cancelling -> Cancelled
job.cancel()           // طلب إلغاء
job.join()             // انتظار الاكتمال
job.cancelAndJoin()    // إلغاء + انتظار

(3) Dispatcher — جدولة الخيوط

KOTLIN
// Dispatchers.Default: عمل كثيف المعالج (التوازي = أنوية المعالج)
launch(Dispatchers.Default) { computeOrderTax() }

// Dispatchers.IO: عمليات الإدخال/الإخراج المانعة (حتى 64 خيط)
launch(Dispatchers.IO) { fetchDataFromDb() }

// Dispatchers.Main: خيط واجهة المستخدم (Android/Swing)
launch(Dispatchers.Main) { updateUI() }

// مجدول مخصص
val orderDispatcher = Executors.newFixedThreadPool(8).asCoroutineDispatcher()
launch(orderDispatcher) { processOrder() }

(4) مقارنة المجدولات

المجدول عدد الخيوط حالة الاستخدام العمليات النموذجية
Default أنوية المعالج كثيف المعالج فرز، حساب
IO حتى 64 إدخال/إخراج مانع شبكة، قاعدة بيانات
Main 1 تحديث واجهة المستخدم Android/سطح المكتب
مخصص مخصص احتياجات محددة تجمع خيوط معزول

6. launch مقابل async

(1) launch — أطلق وانسَ

KOTLIN
// launch: أطلق وانسَ (يرجع Job، لا نتيجة)
val job: Job = launch {
    delay(1_000)
    println("Background work done")
}
job.join()  // انتظار الاكتمال

(2) async — انتظر النتيجة

KOTLIN
// async: يرجع Deferred<T> (كائن Job شبيه بالمستقبل)
val deferred: Deferred<Order> = async {
    fetchOrder("ORD-001")
}
val order = deferred.await()  // تعليق حتى تكون النتيجة جاهزة

(3) التركيب المتزامن

KOTLIN
suspend fun processOrderConcurrently(id: String): EnrichedOrder = coroutineScope {
    // انطلاق بالتوازي ضمن coroutineScope
    val orderDeferred = async { fetchOrder(id) }
    val customerDeferred = async { fetchCustomer(id) }
    val inventoryDeferred = async { checkInventory(id) }

    // انتظار جميع النتائج
    val order = orderDeferred.await()
    val customer = customerDeferred.await()
    val inventory = inventoryDeferred.await()

    EnrichedOrder(order, customer, inventory)
}

(4) مقارنة launch مع async

البُعد launch async
قيمة الإرجاع Job Deferred<T>
الحصول على النتيجة غير متاح .await()
معالجة الاستثناءات يُنشر للأب مخزن في Deferred
حالة الاستخدام آثار جانبية (تسجيل، إشعارات) تحتاج قيمة إرجاع
التشبيه Thread.start() CompletableFuture

7. معالجة استثناءات الكوروتينات

KOTLIN
// try-catch في الكوروتين
launch {
    try {
        fetchOrder(id)
    } catch (e: Exception) {
        logger.error("Failed to fetch order", e)
    }
}

// CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
    logger.error("Coroutine error", exception)
}

launch(handler) {
    fetchOrder(id)  // الاستثناءات غير الملتقطة يعالجها المعالج
}

// SupervisorJob: فشل الابن لا يلغي الإخوة
coroutineScope {
    val supervisor = SupervisorJob()
    with(supervisor) {
        launch { throw Exception("Child 1 fails") }  // فقط هذا الابن يفشل
        launch { delay(100); println("Child 2 still runs") }  // الأخ يستمر
    }
}

8. مثال كامل: معالجة OrderProcessor غير المتزامنة

KOTLIN
// ============================================
// OrderProcessor - معالجة غير متزامنة مع الكوروتينات
// الميزة: إثراء الطلبات المتزامن
// ============================================

import kotlinx.coroutines.*

data class Order(val id: String, val total: Double, val customerId: String, var customerName: String? = null)
data class Customer(val id: String, val name: String, val tier: String)

// محاكاة عمليات غير متزامنة
suspend fun fetchOrder(id: String): Order {
    delay(100)  // محاكاة استعلام قاعدة بيانات
    return Order(id, 299.99 + id.substring(4).toInt() * 100, "CUST-${id.substring(4)}")
}

suspend fun fetchCustomer(id: String): Customer {
    delay(150)  // محاكاة استدعاء API
    return Customer(id, "Customer-$id", if (id.endsWith("1")) "VIP" else "STANDARD")
}

suspend fun checkInventory(orderId: String): Boolean {
    delay(80)  // محاكاة فحص المخزون
    return true
}

// معالجة متسلسلة
suspend fun processSequential(id: String): Order {
    val order = fetchOrder(id)          // 100ms
    val customer = fetchCustomer(order.customerId)  // 150ms
    val available = checkInventory(id)  // 80ms
    // الإجمالي: ~330ms
    return order.copy(customerName = customer.name)
}

// معالجة متزامنة
suspend fun processConcurrent(id: String): Order = coroutineScope {
    val orderDeferred = async { fetchOrder(id) }
    val order = orderDeferred.await()

    // جلب العميل والمخزون بالتوازي
    val customerDeferred = async { fetchCustomer(order.customerId) }
    val inventoryDeferred = async { checkInventory(id) }

    val customer = customerDeferred.await()
    val available = inventoryDeferred.await()
    // الإجمالي: ~100ms + ~150ms = ~250ms (العميل والمخزون بالتوازي)

    if (!available) throw RuntimeException("Inventory unavailable for $id")
    order.copy(customerName = "${customer.name} (${customer.tier})")
}

// معالجة دفعية
suspend fun processBatch(orderIds: List<String>): List<Order> = coroutineScope {
    orderIds.map { id ->
        async { processConcurrent(id) }
    }.awaitAll()
}

fun main() = runBlocking {
    val orderIds = listOf("ORD-001", "ORD-002", "ORD-003", "ORD-004", "ORD-005")

    // قياس التسلسلي
    val seqStart = System.currentTimeMillis()
    val seqResults = orderIds.map { processSequential(it) }
    val seqTime = System.currentTimeMillis() - seqStart
    println("Sequential: ${seqTime}ms for ${seqResults.size} orders")

    // قياس المتزامن
    val conStart = System.currentTimeMillis()
    val conResults = processBatch(orderIds)
    val conTime = System.currentTimeMillis() - conStart
    println("Concurrent: ${conTime}ms for ${conResults.size} orders")

    // طباعة النتائج
    println("\n=== Processed Orders ===")
    conResults.forEach { order ->
        println("  ${order.id}: \$${order.total} USD | ${order.customerName}")
    }

    // التزامن المهيكل: خطأ في واحد يلغي الكل
    println("\n=== Error Handling ===")
    try {
        coroutineScope {
            launch { delay(200); println("Task 1 done") }
            launch { delay(100); throw RuntimeException("Task 2 failed!") }
            launch { delay(300); println("Task 3 done") }
        }
    } catch (e: RuntimeException) {
        println("Caught: ${e.message}")
    }
}

المخرجات:

TEXT 📖 للعرض فقط
Sequential: 1650ms for 5 orders
Concurrent: 370ms for 5 orders

=== Processed Orders ===
  ORD-001: $1100 USD | Customer-CUST-001 (VIP)
  ORD-002: $1200 USD | Customer-CUST-002 (STANDARD)
  ORD-003: $1300 USD | Customer-CUST-003 (STANDARD)
  ORD-004: $1400 USD | Customer-CUST-004 (STANDARD)
  ORD-005: $1500 USD | Customer-CUST-005 (STANDARD)

=== Error Handling ===
Task 1 done
Caught: Task 2 failed!

9. أمثلة عملية سريعة

▶ مثال: CoroutineScope و launch

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    // launch يبدأ كوروتين ويعود فورًا (Job)
    val job = launch {
        delay(1000L)
        println("Coroutine completed after 1s")
    }

    println("Job is active: ${job.isActive}")
    println("Main continues...")

    // انتظار اكتمال الكوروتين
    job.join()
    println("After join: ${job.isCompleted}")
}

**المخرجات:

TEXT 📖 للعرض فقط
Job is active: true
Main continues...
Coroutine completed after 1s
After join: true

▶ مثال: async و await للقيم

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    // async يبدأ كوروتين ويعيد Deferred<T>
    val deferred1 = async {
        delay(1000L)
        "Result 1"
    }
    val deferred2 = async {
        delay(500L)
        "Result 2"
    }

    println("Waiting for both...")
    val result1 = deferred1.await()
    val result2 = deferred2.await()

    println("Got: $result1 and $result2")

    // awaitAll لكل الـ Deferreds
    val deferreds = listOf(
        async { delay(100); "A" },
        async { delay(200); "B" },
        async { delay(150); "C" }
    )
    val allResults = deferreds.awaitAll()
    println("All: $allResults")
}

**المخرجات:

TEXT 📖 للعرض فقط
Waiting for both...
Got: Result 1 and Result 2
All: [A, B, C]

▶ مثال: structured concurrency

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    // coroutineScope ينتظر جميع الأطفال
    val result = coroutineScope {
        val n1 = async { delay(500); "A" }.await()
        val n2 = async { delay(300); "B" }.await()
        "$n1-$n2"
    }
    println("Structured result: $result")

    // supervisorScope - فشل طفل لا يلغي البقية
    supervisorScope {
        val job1 = launch {
            delay(100)
            println("Child 1 done")
        }
        val job2 = launch {
            delay(50)
            throw RuntimeException("Oops!")
        }
        val job3 = launch {
            delay(200)
            println("Child 3 still runs!")
        }

        try {
            job2.join()
        } catch (e: Exception) {
            println("Caught: ${e.message}")
        }

        job1.join()
        job3.join()
    }
    println("All done")
}

**المخرجات:

TEXT 📖 للعرض فقط
Structured result: A-B
Child 1 done
Caught: Oops!
Child 3 still runs!
All done

▶ مثال: Cancellation و Timeout

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(10) { i ->
            println("Working $i...")
            delay(500L)
        }
    }

    // إلغاء بعد 1.2 ثانية
    delay(1200L)
    println("Cancelling...")
    job.cancelAndJoin()
    println("Cancelled: ${!job.isActive}")

    // withTimeout - إلغاء تلقائي بعد مهلة
    val result = withTimeoutOrNull(1500L) {
        repeat(5) { i ->
            println("Task $i...")
            delay(400L)
        }
        "Completed"
    }
    println("Result with timeout: $result")
}

**المخرجات:

TEXT 📖 للعرض فقط
Working 0...
Working 1...
Working 2...
Cancelling...
Cancelled: true
Task 0...
Task 1...
Task 2...
Result with timeout: null

▶ مثال: Dispatchers و CoroutineContext

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    // Dispatchers.Default - CPU-bound
    val cpuResult = withContext(Dispatchers.Default) {
        // حساب ثقيل على CPU
        var sum = 0L
        for (i in 1..10_000_000) sum += i
        "Sum: $sum"
    }
    println(cpuResult)

    // Dispatchers.IO - I/O-bound
    val ioResult = withContext(Dispatchers.IO) {
        // محاكاة I/O
        delay(500L)
        "I/O done"
    }
    println(ioResult)

    // Dispatchers.Main - UI (يحتاج تهيئة في Android)
    // مع kotlinx-coroutines-test في وحدة، Main يعمل
    // val mainResult = withContext(Dispatchers.Main) { "UI" }

    // CoroutineScope مخصص
    val customScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
    val deferred = customScope.async {
        delay(200)
        "Custom scope"
    }
    println(deferred.await())
    customScope.cancel()
}

**المخرجات:

TEXT 📖 للعرض فقط
Sum: 50000005000000
I/O done
Custom scope

▶ مثال: Flow للبرمجة التفاعلية

KOTLIN
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    // Flow بارد - تنفيذ عند الجمع
    val numberFlow: Flow<Int> = flow {
        for (i in 1..5) {
            delay(100)
            emit(i)
        }
    }

    // collect - استهلاك كل قيمة
    println("--- First collector ---")
    numberFlow.collect { value ->
        println("Got: $value")
    }

    // map و filter
    val transformedFlow = numberFlow
        .map { it * 10 }
        .filter { it > 20 }

    println("--- Transformed ---")
    transformedFlow.collect { println("Transformed: $it") }

    // reduce - تجميع كل القيم
    val sumFlow = numberFlow.reduce { acc, value -> acc + value }
    println("Sum: $sumFlow")

    // Flow من قائمة
    val listFlow = listOf(1, 2, 3, 4, 5).asFlow()
    listFlow.collect { println("From list: $it") }
}

**المخرجات:

TEXT 📖 للعرض فقط
--- First collector ---
Got: 1
Got: 2
Got: 3
Got: 4
Got: 5
--- Transformed ---
Transformed: 30
Transformed: 40
Transformed: 50
Reduced: 15
From list: 1
From list: 2
From list: 3
From list: 4
From list: 5

▶ مثال: معالجة الأخطاء في Coroutines

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    // try-catch مع launch
    val job = launch {
        try {
            delay(500)
            throw RuntimeException("Something went wrong")
        } catch (e: Exception) {
            println("Caught: ${e.message}")
        }
    }
    job.join()

    // CoroutineExceptionHandler - معالج عام
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Handler caught: ${exception.message}")
    }

    val scope = CoroutineScope(Dispatchers.Default + SupervisorJob() + handler)
    scope.launch {
        throw IllegalStateException("Handled error")
    }

    delay(100)
    scope.cancel()
    println("Scope cancelled")
}

**المخرجات:

TEXT 📖 للعرض فقط
Caught: Something went wrong
Handler caught: Handled error
Scope cancelled

❓ أسئلة شائعة

س ما الفرق بين الكوروتينات والخيوط الافتراضية؟
ج الكوروتينات هي تطبيق كوتلن في وضع المستخدم يتطلب علامات suspend؛ الخيوط الافتراضية هي على مستوى JVM (JDK 21+) ولا تتطلب تغييرات في الكود. الكوروتينات أكثر مرونة (متعددة المنصات)، بينما الخيوط الافتراضية أكثر شفافية (لا تغييرات في المكتبات).
س ما الفرق بين delay() و Thread.sleep()؟
ج delay() تعلق الكوروتين وتحرر الخيط؛ Thread.sleep() تحظر الخيط بالكامل. في الكوروتينات، استخدم دائماً delay، لا تستخدم أبداً Thread.sleep.
س متى يجب أن أستخدم runBlocking؟
ج بشكل أساسي في دوال main() والاختبارات كنقطة دخول الكوروتين. تجنبه في كود الإنتاج — إنه يحظر الخيط الحالي.
س كيف أنظّف الموارد بعد إلغاء الكوروتين؟
ج استخدم try-finally أو دوال use. نفّذ التنظيف في الكتلة finally عند إلغاء الكوروتين لضمان تحرير الموارد.
س متى يُطرح استثناء async؟
ج استثناء async مخزن في Deferred ويُطرح فقط عند استدعاء .await(). إذا لم تنتظر أبداً، يُفقد الاستثناء بصمت.
س متى يجب أن أستخدم SupervisorJob؟
ج عندما لا يجب أن يؤثر فشل ابن على إخوته. مثلاً، في كود واجهة المستخدم، فشل طلب واحد لا يجب أن يلغي الآخرين. في coroutineScope العادي، فشل ابن واحد يلغي جميع الإخوة.

📖 ملخص


📝 تمارين

  1. مبتدئ (⭐): استخدم runBlocking + launch لبدء 3 كوروتينات، كل منها تأخير وقتاً مختلفاً وتطبع رسالة. تلميح: launch { delay(N); println(...) }
  2. متوسط (⭐⭐): استخدم coroutineScope + async لجلب معلومات الطلب والعميل بالتوازي، ودمجهما في طلب كامل. تلميح: async { fetchOrder }، await()
  3. متقدم (⭐⭐⭐): نفّذ دالة جلب طلب مع مهلة وإعادة محاولة: مهلة 3 ثوانٍ، حتى 3 محاولات، تراجع أسي. تلميح: withTimeout + حلقة retry

← السابق | التالي →

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%