الكوروتينات في Kotlin تمكّنك من كتابة رمز غير متزامن نظيف ومبسط للحفاظ على استجابة تطبيقك وإدارة المهام الطويلة المدى، مثل مكالمات الشبكة أو عمليات القرص.
يقدّم هذا الموضوع نظرة تفصيلية على الكوروتينات على Android. إذا كنت على دراية بشرائط الكوروتين، تأكد من قراءة الكوروتينات في Kotlin على Android قبل قراءة هذا الموضوع.
إدارة المهام الطويلة المدى
تعتمد الكوروتينات على وظائف منتظمة من خلال إضافة عمليتين للتعامل مع
المهام الطويلة المدى. بالإضافة إلى invoke
(أو call
) وreturn
،
تضيف الكوروتينات suspend
وresume
:
- يوقِف
suspend
مؤقتًا تنفيذ الكوروتين الحالي ويحفظ جميع القيم المحلية. المتغيرات. - يواصل "
resume
" تنفيذ الكوروتين المعلّق من المكان. حيث تم تعليقه.
لا يمكنك استدعاء دوال suspend
إلا من دوال suspend
الأخرى أو
باستخدام إحدى أدوات إنشاء الكوروتين مثل launch
لبدء كوروتين جديد.
يوضح المثال التالي تنفيذ كوروتين بسيط مهمة افتراضية طويلة الأمد وهي:
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("https://developer.android.com") // Dispatchers.IO for `get`
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }
في هذا المثال، لا يزال يتم تشغيل get()
في سلسلة التعليمات الرئيسية، ولكنه يعلّق
الكوروتين قبل بدء طلب الشبكة. عندما يطلب طلب الشبكة
مكتمل، يستأنف get
الكوروتين المعلّق بدلاً من استخدام معاودة الاتصال.
لإعلام سلسلة التعليمات الرئيسية
تستخدم لغة Kotlin إطارًا مكدّسًا لتحديد الدالة التي تعمل باستمرار. مع أي متغيرات محلية. عند تعليق الكوروتين، فإن الحزمة الحالية يتم نسخ الإطار وحفظه لاستخدامه في وقت لاحق. عند الاستئناف، يصبح إطار الحزمة من حيث تم حفظها، وستبدأ الدالة في العمل مرة أخرى. رغم أن الرمز قد يبدو كحظر تسلسلي عادي فإن الكوروتين يضمن أن يتجنب طلب الشبكة حظر سلسلة التعليمات الرئيسية.
استخدام الكوروتين للحفاظ على السلامة الرئيسية
تستخدِم الكوروتينات في لغة Kotlin المرسِلين لتحديد سلاسل المحادثات التي يتم استخدامها. وتنفيذ الكوروتين. ولتشغيل الرمز خارج سلسلة التعليمات الرئيسية، يمكنك إخبار Kotlin الكوروتينات لأداء العمل على المرسِل التلقائي أو إدراج الإدخال. ضِمن لغة Kotlin، يجب أن تعمل جميع الكوروتينات في جهاز إرسال البيانات، حتى إذا كانت تعمل على سلسلة التعليمات الرئيسية. يمكن للكوروتينات تعليق أنفسهم، ويرسل المرسل مسئولون عن استئنافها.
لتحديد مكان تشغيل الكوروتينات، توفر لغة Kotlin ثلاثة مرسِلين التي يمكنك استخدامها:
- Dispatchers.Main: استخدِم هذا المرسِل لتشغيل الكوروتين على الجهاز الرئيسي
سلسلة محادثات Android. يجب استخدامه فقط للتفاعل مع واجهة المستخدم
وتنفيذ عمل سريع. تشمل الأمثلة استدعاء دوال
suspend
والجري عمليات إطار عمل واجهة المستخدم على Android وتحديث كائناتLiveData
. - Dispatchers.IO - هذا المرسِل محسَّن لأداء القرص أو الشبكة وحدات الإدخال والإخراج خارج سلسلة التعليمات الرئيسية. تتضمن الأمثلة استخدام مكوِّن الغرفة، القراءة من الملفات أو الكتابة إليها، وتشغيل أي عمليات على الشبكة.
- Dispatchers.Default - هذا المرسِل محسّن لتنفيذ يعمل ذلك بكثافة وحدة المعالجة المركزية (CPU) خارج نطاق سلسلة التعليمات الرئيسية. تتضمن أمثلة حالات الاستخدام فرز سرد وتحليل JSON.
لمتابعة المثال السابق، يمكنك استخدام المرسلين لإعادة تحديد
get
. داخل نص get
، اطلب withContext(Dispatchers.IO)
من أجل
إنشاء مجموعة يتم تشغيلها على مجموعة سلاسل محادثات IO. أي رمز تضعه بداخله
يتم تنفيذ الحظر دائمًا عبر مرسِل IO
. بما أنّ "withContext
" هي في حد ذاتها
دالة التعليق، فإن الدالة get
هي أيضًا دالة التعليق.
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("developer.android.com") // Dispatchers.Main
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = // Dispatchers.Main
withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block)
/* perform network IO here */ // Dispatchers.IO (main-safety block)
} // Dispatchers.Main
}
باستخدام الكوروتينات، يمكنك إرسال سلاسل المحادثات والتحكّم فيها بدقة. لأنّ
تتيح لك withContext()
التحكّم في مجموعة سلاسل المحادثات لأي سطر من الرموز البرمجية بدون
وتقديم استدعاءات، يمكنك تطبيقها على وظائف صغيرة جدًا مثل القراءة
من قاعدة بيانات أو تنفيذ طلب شبكة. من الممارسات الجيدة استخدام
withContext()
للتأكد من أن كل دالة main-safe، مما يعني أنك
يمكننا استدعاء الدالة من سلسلة التعليمات الرئيسية. بهذه الطريقة، لا يحتاج المتصل إلى
فكر في مؤشر الترابط الذي يجب استخدامه لتنفيذ الدالة.
في المثال السابق، يتم تنفيذ fetchDocs()
في سلسلة التعليمات الرئيسية. مع ذلك،
يمكنه الاتصال بـ get
بأمان، والذي ينفذ طلب شبكة في الخلفية.
وبما أنّ الكوروتينات يدعم suspend
وresume
، فإنّ الكوروتينات
تُستأنف سلسلة المحادثات بالحصول على النتيجة get
فور حلّ حظر withContext
.
تم.
أداء withContext()
withContext()
لا يضيف تكاليف إضافية إضافية مقارنة مع خطوات معاودة الاتصال المكافئة المستندة إلى
التنفيذ. بالإضافة إلى ذلك، من الممكن تحسين مكالمات "withContext()
"
بخلاف تنفيذ مكافئ مستند إلى معاودة الاتصال في بعض المواقف. بالنسبة
على سبيل المثال، إذا كانت هناك دالة تقوم بإجراء عشر اتصالات إلى الشبكة، فيمكنك إخبار Kotlin
لتبديل سلاسل المحادثات مرة واحدة فقط باستخدام علامة withContext()
خارجية. بعد ذلك، على الرغم من
تستخدِم مكتبة الشبكات withContext()
عدة مرات، وستظل على المستوى نفسه.
المرسل ويتجنب تبديل سلاسل المحادثات. بالإضافة إلى ذلك، تُحسّن لغة Kotlin التبديل
بين Dispatchers.Default
وDispatchers.IO
لتجنُّب التبديل بين سلاسل المحادثات
كلما أمكن ذلك.
بدء تناول الكوروتين
يمكنك بدء الكوروتين بإحدى طريقتين:
launch
يبدأ كوروتينًا جديدًا ولا يعرض النتيجة إلى المتصل. أي تقييم العمل الذي يعتبر "إشعال نار ونسيان" لبدء استخدام "launch
".async
تبدأ كوروتينًا جديدًا وتسمح لك بعرض نتيجة بتعليق تسمىawait
.
في العادة، عليك launch
على كوروتين جديد من دالة عادية،
حيث لا يمكن للدالة العادية استدعاء await
. استخدام "async
" فقط داخل المتجر
كوروتين آخر أو عندما يكون داخل دالة تعليق وتنفيذ
انحلال متوازٍ.
الانحلال المتواز
يجب إيقاف جميع الكوروتينات التي تبدأ داخل دالة suspend
عندما
التي تُرجعها الدالة، لذا تحتاج على الأرجح إلى ضمان أن تكون تلك الكوروتينات
والانتهاء قبل العودة. باستخدام التزامن الهيكلي في Kotlin، يمكنك تحديد
coroutineScope
الذي يطلق كوروتينًا واحدًا أو أكثر. بعد ذلك، باستخدام "await()
"
(لكوروتين واحد) أو awaitAll()
(لكوروتينات متعددة)، يمكنك
أن هذه الكوروتينات تنتهي قبل العودة من الدالة.
على سبيل المثال، لنحدِّد coroutineScope
الذي يجلب مستندَين.
بشكل غير متزامن. من خلال طلب الرقم await()
عند كل مرجع مؤجَّل، نضمن لك
تنتهي عمليتان async
قبل عرض قيمة:
suspend fun fetchTwoDocs() =
coroutineScope {
val deferredOne = async { fetchDoc(1) }
val deferredTwo = async { fetchDoc(2) }
deferredOne.await()
deferredTwo.await()
}
يمكنك أيضًا استخدام السمة awaitAll()
في المجموعات، كما هو موضّح في المثال التالي:
suspend fun fetchTwoDocs() = // called on any Dispatcher (any thread, possibly Main)
coroutineScope {
val deferreds = listOf( // fetch two docs at the same time
async { fetchDoc(1) }, // async returns a result for the first doc
async { fetchDoc(2) } // async returns a result for the second doc
)
deferreds.awaitAll() // use awaitAll to wait for both network requests
}
على الرغم من أنّ fetchTwoDocs()
تطلق كوروتينات جديدة باستخدام async
، لا يمكن اعتبار الدالة
تستخدم awaitAll()
لانتظار انتهاء الكوروتينات التي تم إطلاقها قبل
تَعُودْ. ومع ذلك، تجدر الإشارة إلى أنه حتى لو لم نكُن قد استدعينا awaitAll()
، سيتم
لا تستأنف أداة الإنشاء coroutineScope
الكوروتين الذي يسمى
fetchTwoDocs
حتى بعد اكتمال جميع الكوروتينات الجديدة
بالإضافة إلى ذلك، تسجّل coroutineScope
أي استثناءات تطرحها الكوروتينات.
ويعيد توجيهه إلى المتصل.
ولمزيد من المعلومات عن الانحلال المتواز، يُرجى مراجعة إنشاء دوال التعليق:
مفاهيم الكوروتين
كوروتين سكوب
CoroutineScope
يتتبع أي كوروتين ينتج عنه باستخدام launch
أو async
. تشير رسالة الأشكال البيانية
يمكن إلغاء العمل الجاري (مثل الكوروتينات الجاري) من خلال الاتصال
scope.cancel()
في أي وقت. وفي Android، توفر بعض مكتبات KTX
"CoroutineScope
" الخاصة بصفوف معيّنة من مراحل النشاط على سبيل المثال:
لدى ViewModel
viewModelScope
,
وLifecycle
لديه lifecycleScope
.
وعلى عكس المُرسل، لا يدير CoroutineScope
الكوروتينات.
يتم استخدام viewModelScope
أيضًا في الأمثلة الواردة في
سلاسل المحادثات في الخلفية على Android باستخدام الكوروتين
ومع ذلك، إذا كنت بحاجة إلى إنشاء CoroutineScope
الخاصة بك للتحكم في
دورة حياة الكوروتينات في طبقة معيّنة من تطبيقك، يمكنك إنشاء واحد
على النحو التالي:
class ExampleClass {
// Job and Dispatcher are combined into a CoroutineContext which
// will be discussed shortly
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun exampleMethod() {
// Starts a new coroutine within the scope
scope.launch {
// New coroutine that can call suspend functions
fetchDocs()
}
}
fun cleanUp() {
// Cancel the scope to cancel ongoing coroutines work
scope.cancel()
}
}
لا يمكن للنطاق المُلغى إنشاء المزيد من الكوروتينات. لذلك، يجب عليك
طلب "scope.cancel()
" فقط عندما يتحكّم الصف الدراسي في مراحل نشاطه
يتعرض لتدميرها. عند استخدام الدالة viewModelScope
،
يلغي الصف ViewModel
نطاقًا تلقائيًا لك في طريقة onCleared()
في ViewModel.
الوظيفة
Job
هو التعامل مع الكوروتين. كلّ كوروتين تنشئه باستخدام "launch
"
أو تعرض async
المثيل Job
الذي يعرّف بشكلٍ فريد
الكوروتين ويدير دورة حياته. يمكنك أيضًا تمرير Job
إلى
CoroutineScope
لإدارة دورة حياتها بشكل أكبر، كما هو موضح في ما يلي
مثال:
class ExampleClass {
...
fun exampleMethod() {
// Handle to the coroutine, you can control its lifecycle
val job = scope.launch {
// New coroutine
}
if (...) {
// Cancel the coroutine started above, this doesn't affect the scope
// this coroutine was launched in
job.cancel()
}
}
}
سياق الكوروتين
CoroutineContext
تُحدِّد سلوك الكوروتين باستخدام المجموعة التالية من العناصر:
Job
: للتحكم في دورة حياة الكوروتين.CoroutineDispatcher
: تعمل عمليات الإرسال إلى سلسلة التعليمات المناسبة.CoroutineName
: اسم الكوروتين، وهو مفيد لتصحيح الأخطاءCoroutineExceptionHandler
: يعالج الاستثناءات غير المعروفة.
بالنسبة إلى الكوروتينات الجديدة التي يتم إنشاؤها ضمن نطاق، يتم استخدام مثيل Job
جديد
المخصّص للكوروتين الجديد، وعناصر CoroutineContext
الأخرى
مكتسَبة من النطاق الذي يتضمّنه. يمكنك إلغاء الإعدادات
من خلال تمرير قيمة CoroutineContext
جديدة إلى launch
أو async
الأخرى. يُرجى العِلم أنّ ضبط Job
إلى launch
أو async
ليس له أي تأثير،
بالنسبة إلى مثيل جديد من Job
، يتم دائمًا تخصيص كوروتين جديد له.
class ExampleClass {
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun exampleMethod() {
// Starts a new coroutine on Dispatchers.Main as it's the scope's default
val job1 = scope.launch {
// New coroutine with CoroutineName = "coroutine" (default)
}
// Starts a new coroutine on Dispatchers.Default
val job2 = scope.launch(Dispatchers.Default + CoroutineName("BackgroundCoroutine")) {
// New coroutine with CoroutineName = "BackgroundCoroutine" (overridden)
}
}
}
موارد إضافية حول الكوروتينات
لمزيد من موارد الكوروتينات، اطلع على الروابط التالية: