Kotlin: شرح الكوروتينات في كوتلن
آخر تحديث: 2026-08-26
الكوروتينات هي جوهر البرمجة غير المتزامنة في كوتلن — دالة suspend تتيح لتشارلي كتابة عمليات غير متزامنة بصياغة متزامنة، بينما التزامن المهيكل يضمن عدم تسرب أي كوروتين.
1. ما ستتعلمه
- الكوروتينات مقابل الخيوط: خفيفة الوزن، غير مانعة، تزامن مهيكل
- دوال
suspend: "صياغة متزامنة" في العالم غير المتزامن CoroutineScope/Job/Dispatcher: الأعمدة الثلاثة للتزامن المهيكلlaunch(أطلق وانسَ) مقابلasync(انتظر النتيجة)- تشارلي في العمل:
OrderProcessorمتزامن معasync+await
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) مخطط مبدأ الكوروتين
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 ميجابايت)
- دوال
suspendتجعل الكود غير المتزامن يُقرأ كمتزامن — إنها تعلق، لا تحظر - التزامن المهيكل:
coroutineScopeيضمن اكتمال أو إلغاء جميع الكوروتينات الأبناء launchيطلق وينسى؛asyncينتظر قيمة إرجاعDispatcherيتحكم في تجمع الخيوط المنفذ: Default / IO / Main- معالجة الاستثناءات:
try-catch/CoroutineExceptionHandler/SupervisorJob
📝 تمارين
- مبتدئ (⭐): استخدم
runBlocking+launchلبدء 3 كوروتينات، كل منها تأخير وقتاً مختلفاً وتطبع رسالة. تلميح:launch { delay(N); println(...) } - متوسط (⭐⭐): استخدم
coroutineScope+asyncلجلب معلومات الطلب والعميل بالتوازي، ودمجهما في طلب كامل. تلميح:async { fetchOrder }،await() - متقدم (⭐⭐⭐): نفّذ دالة جلب طلب مع مهلة وإعادة محاولة: مهلة 3 ثوانٍ، حتى 3 محاولات، تراجع أسي. تلميح:
withTimeout+ حلقةretry