Coroutine Builder — دوال Kotlin Coroutines التي تنشئ وتشغل الكوروتينات، وتحدد طريقة تنفيذها. تغطي البنّاءات launch و async و runBlocking و produce سيناريوهات مختلفة: من المهام الخلفية إلى الحسابات المتوازية مع إرجاع النتيجة. وفقًا لـ JetBrains, 2024، Coroutine Builder هو أساس نموذج الكوروتينات، حيث يوفر التزامن المنظم وإدارة دورة الحياة.
النقاط الرئيسية
Coroutine Builder هي دالة توسعة في Kotlin تأخذ CoroutineScope وكتلة suspend، وتنشئ وتشغل كوروتين جديد. يحدد كل باني كيفية تنفيذ الكوروتين: مع أو بدون إرجاع نتيجة، مع حجب الخيط أو بشكل غير متزامن. البنّاءات هي نقاط الدخول إلى نموذج الكوروتينات في اللغة.
تعمل جميع البنّاءات عبر CoroutineScope، الذي يدير دورة حياة الكوروتينات الفرعية. عند إلغاء النطاق، يتم إلغاء جميع الكوروتينات المشغلة عبره تلقائيًا — هذا هو مبدأ التزامن المنظم. يمنع هذا النهج تسرب الكوروتينات ويضمن إنهاءً قابلًا للتنبؤ.
import kotlinx.coroutines.*
fun main() = runBlocking {
// تعمل البنّاءات داخل CoroutineScope
val job = launch {
delay(1000L)
println("العالم!")
}
println("مرحبًا،")
job.join()
}
يوفر Kotlin أربعة بناة كوروتينات مدمجة: launch و async و runBlocking و produce. لكل منها نوع إرجاعه الخاص ومجال تطبيقه. لتطوير Android المحمول، البنّاءان الرئيسيان هما launch و async — يعملان بطريقة غير حاجبة ويتكاملان مع المكونات المعمارية.
| الباني | نوع الإرجاع | حجب الخيط | السيناريو |
|---|---|---|---|
| launch | Job | لا | مهام fire-and-forget |
| async | Deferred<T> | لا | حسابات متوازية |
| runBlocking | T | نعم | اختبارات، دالة main |
| produce | ReceiveChannel<E> | لا | نقل بيانات (مهمل) |
يقبل كل باني معاملات إضافية: CoroutineStart (استراتيجية التشغيل)، CoroutineContext (الموزع، الاستثناءات) وكتلة كود مسماة. افتراضيًا، يبدأ الكوروتين فورًا (CoroutineStart.DEFAULT).
launch هو الباني الأكثر استخدامًا في تطوير Android. يشغل كوروتين لا يُرجع نتيجة، ويعيد كائن Job لإدارة دورة حياته. هذا هو الخيار المثالي للعمليات التي تحتاج فقط إلى تأثير جانبي: الحفظ في قاعدة البيانات، إرسال التحليلات، تحديث واجهة المستخدم.
يقبل باني launch CoroutineScope و CoroutineContext اختياري وكتلة suspend. يسمح Job الذي يُرجع بإلغاء الكوروتين أو انتظار اكتماله أو التحقق من حالته.
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
val job: Job = scope.launch(CoroutineStart.LAZY) {
val data = fetchFromNetwork()
saveToDatabase(data)
}
job.start()
job.join()
المعامل CoroutineStart.LAZY يؤجل التنفيذ حتى استدعاء صريح لـ start() أو join(). هذا مفيد للتهيئة البطيئة والتنفيذ الشرطي. للتنفيذ الفوري القياسي، يُستخدم CoroutineStart.DEFAULT أو يُحذف المعامل.
async هو باني يُرجع Deferred<T> — وعد غير متزامن بنتيجة. استدعاء await() يعلق الكوروتين حتى الحصول على النتيجة، دون حجب الخيط. هذه هي الآلية الرئيسية للمهام المتوازية في كوروتينات Kotlin.
async فعال بشكل خاص عندما تحتاج لتنفيذ عمليات متعددة مستقلة في وقت واحد. على عكس الاستدعاءات التسلسلية لدوال suspend، يقوم async بتشغيل الكوروتينات بالتوازي، مما يقلل وقت التنفيذ الإجمالي.
suspend fun fetchUserData(): UserData {
val deferred1 = CoroutineScope(Dispatchers.IO).async { api.getProfile() }
val deferred2 = CoroutineScope(Dispatchers.IO).async { api.getSettings() }
val deferred3 = CoroutineScope(Dispatchers.IO).async { api.getNotifications() }
return UserData(
profile = deferred1.await(),
settings = deferred2.await(),
notifications = deferred3.await()
)
}
Deferred يرث من Job، لذلك يدعم async جميع عمليات دورة الحياة: الإلغاء، انتظار الاكتمال، معالجة الاستثناءات. عند إلغاء النطاق، يتم إلغاء كوروتينات Deferred الفرعية تلقائيًا.
runBlocking هو الباني الوحيد الذي يحجب الخيط الحالي حتى اكتمال الكوروتين. ينشئ CoroutineScope جديد ويشغل الكوروتين المُمرر، محجبًا الخيط المستدعي. يُستخدم في نقاط الدخول main()، في الاختبارات، وعند التكامل مع كود حاجب.
runBlocking مبرر في ثلاثة سيناريوهات: نقطة دخول التطبيق (main)، اختبارات الوحدات لدوال suspend، والتكامل مع المكتبات القائمة على الاستدعاءات حيث لا يمكن استخدام suspend. في كود Android الإنتاجي، استخدام runBlocking على الخيط الرئيسي غير موصى به بشدة.
class CoroutineTest {
@Test
fun `test suspend function`() = runBlocking {
val result = mySuspendFunction()
assertEquals("expected", result)
}
}
للاختبارات، يُوصى باستخدام kotlinx-coroutines-test مع TestCoroutineDispatcher بدلاً من runBlocking — وهذا يوفر التحكم في الوقت ويتجنب الحجب في بيئة الاختبار.
يعتمد اختيار Coroutine Builder على النتيجة المعادة وسيناريو التنفيذ. إذا كانت العملية لا تتطلب إرجاع بيانات — استخدم launch. إذا كنت بحاجة لنتيجة عملية غير متزامنة — استخدم async. استخدم runBlocking فقط للجسر، واستبدل produce بـ Flow للتدفقات التفاعلية.
في مشاريع Android التي تستخدم Kotlin Coroutines، الزوج الرئيسي من البنّاءات هما launch و async. يُستخدم launch في ViewModel و UseCases لتشغيل الكوروتينات، بينما يُستخدم async للطلبات المتوازية للشبكة أو قاعدة البيانات. المكتبات الحديثة (Ktor, Room) تدعم بالفعل دوال suspend، مما يقلل الحاجة للاستخدام المباشر لـ async.
الأسئلة الشائعة
launch يُرجع Job ولا يُرجع نتيجة تنفيذ، بينما async يُرجع Deferred<T> — كائن يمكن الحصول على النتيجة منه عبر await(). يُستخدم launch لعمليات fire-and-forget، وasync للمهام التي تُرجع بيانات.
غير موصى به. runBlocking على الخيط الرئيسي يسبب ANR ويحجب واجهة المستخدم. استخدم lifecycleScope.launch داخل Activity و Fragment — إنه حل مدمج بدون حجب.
باني launch يُرجع كائن Job، الذي يسمح بالتحكم في دورة حياة الكوروتين: الإلغاء (cancel)، انتظار الاكتمال (join)، التحقق من الحالة (isActive, isCompleted, isCancelled).
Deferred<T> هو وعد غير متزامن بنتيجة، يُرجع بواسطة باني async. يرث من Job ويضيف طرقًا: await() للحصول على النتيجة، getCompleted() للوصول غير الحاجب، و getCompletionExceptionOrNull() للتحقق من الاستثناءات.
استخدم المعامل CoroutineStart.LAZY: scope.launch(start = CoroutineStart.LAZY) { ... }. ثم استدعِ job.start() أو job.join() للتنفيذ الفعلي. هذا مفيد للتهيئة البطيئة والتنفيذ الشرطي للكوروتينات.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.