تعرف على Kotlin Coroutines الجزء الأول

قبل ما نخش في الموضوع الرئيسي بتاعنا لازم تكون عارف الطريقة اللي بيشتغل بيها أي تطبيق اندرويد، في الوقت الحالي هنركز على الشئ اللي هيترتب عليه اللي هنشرحه وهو Threads

طب يعني ايه Thread؟ الثريد هو عبارة عن خط زمني بيتم فيها عمل التاسكات اللي انت كتبتها في الكود، التطبيق الواحد بيبقا فيه أكتر من Thread بحيث أنه يكون في خط بيعمل حاجة وخط تاني بيعمل حاجة تانية مثال -للتوضيح فقط- : Thread بيجيب الداتا بتاعت المستخدم من السيرفر، وthread تاني بيجيب الصورة من موقع. الاتنين بيحصل في نفس الوقت ملهمش علاقة بالتاني، ممكن دا ياخد ثانية والتاني ياخد دقيقة. الشئ المش كويس فيه Threads أنها Synchronous، يعني بتمشي خطوة خطوة، لازم تخلص الخطوة كلها عشان تخش على اللي بعدها وهنعرف ليه دا شئ سئ.

لو هنتكلم بقا على ارض الواقع فلنفترض إنك بنيت تطبيق ، أول ما اليوزر بيفتح التطبيق النظام بيقوم بعمل main thread وديه بيتم رسم فيه الviews والأشكال اللي بتظهر على الشاشة .. ديه تعتبر أهم ثريد في التطبيق، عشان هي اللي بتستجيب لليوزر -داس مثلا على زرار- ، فلو أنت جيت عملت تاسك فيها بياخد شوية وقت زي مثلا إنك تجيب داتا من السيرفر فدا هيعمل مشكلة كبيرة وهي إنه هيوقف الثريد كلوا “الشاشة مش هتستجيب خالص” غير لما يجيب الداتا كلها من السيرفر عشان زي ما قولنا فوق الTread بيكون Synchronous، فأي تاسك بياخد أكتر من 16 ملي ثانية لو اتعمل في main thread هيبدأ يظهر لاج، ولو وصل لخمس ثواني النظام بيظهر dialog مكتوب فيها “App not responding”. عملية تفادي الموضوع دا اسمه Main-Safe

حسنًا إذًا سأقوم بعمل Thread جديد كل مرة أريد أن أقوم بتاسك يحتاج الكثير من الوقت. للأسف لا يمكن، الThread يعتبر شئ غالي وقيم، النظام بيستهلك مصادر “Resources” كتيرة عشان يعمل ثريد واحد، وهيكون أكتر أنه يحصل memory leak.

النظام لا يتحمل Threads كتيرة

الحلول المتاحة:

  • Callback: طريقة بدائية، بحيث أنه أول ما يخلص الشغل الكثير يبلغ التطبيق أنه الشغل خلص والداتا موجودة
  • AsyncTask : دا API موجود في الاندرويد ودلوقتي deprecated وجوجل بتقول محدش يستخدمه
  • Reactive programming: ديه فريم ورك زي RxJava بتقعد تراقب العملية وتديني الداتا على طول، هنشرحها بعدين
  • Coroutines: دا موضوعنا النهاردة وهنشرحه بالتفصيل

ايه هو Coroutines؟

الCoroutines بيتقال عليها lightweight thread، يعني هي بيتعمل فيها تاسك كبير بشكل منفصل ومن غير ما يوقف الthreads اللي شغالة بما فيها Main Thread، وفي نفس الوقت هي مش غالية زي thread والنظام ممكن يعملها من غير أي Resources تذكر.

مهم جدًا تعرف أن Coroutines مش Thread إطلاقًا، لكن الثريد بيكون جواه coroutines وممكن يتنقلوا بين الThreads بسهولة، يعني مثلاً أنت بتعمل Network Call في Coroutines في IO Thread -هنشرحها بعدين- وجبت الداتا خلاص عايز تظهرها لليوزر فتبعت الCoroutines للMain thread عشان تظهرها في TextView مثلاً ، وزي ما احنا عارفين أن أي Action بناخده على view مثلاً -()SetText- لازم يكون جواه الMain thread وإلا التطبيق هيقف عشان Wrong thread Exception

مبدأيًا عشان الناس متتلخبطش Coroutines مش موجود للكوتلن بس، هو جزء من لغات برمجة موجودة قبل كدا زي البايثون وRuby والجافا سكربت بس طبعًا بتبقى مختلفة عن بعض.

:Kotlin Coroutines

أخيرًا وصلنا لموضوعنا الاساسي، دي نسخة Kotlin من Coroutines وواحدة من الأسباب اللي تخليك تبدأ تتعلم كوتلن وتسيب الجافا. أي حاجة قولتها فوق تنطبق على Coroutines فممكن نعمل ملخص ليه بنستخدمها:

  • لو عايز نعمل تاسك بياخد وقت طويل من غير ما نوقف Main Thread
  • نعمل network call من الMain thread ونراعيMain safety
  • مش هنضطر نعمل Thread وهنوفر في الميموري والResources بتاعت الجهاز

ملحوظة على تاني نقطة: لازم طبعًا تكون عارف لو عملت network call من الMain thread التطبيق هيقفل وهيديك android.os.NetworkOnMainThreadException

طب كل Coroutine واحد مش Task كامل لأ هو عبارة عن تاسكات فرعية، يعني هو بيقوم بتقسيم الكود بتاعك إلى تاسكات، الثريد بينفذ منها شوية وممكن بعديها Suspend ويوقفها ووممكن كمان شوية يعملها Resume

أخيرًا هنبدأ نكتب كود!، في الأول هنبدأ نكتب حجات بسيط جدًا وفي الاجزاء الجاية هتزداد صعوبة لحين نوصل إننا نبني Coroutines بيستخدم في تطبيقات الحياة اليومية

: Suspending functions

في البداية لازم نعرف أهم حاجة وهي Suspending function وهي عكس تماما لBlocking function اللي بتوقف كل حاجة لحد ما تتنفذ كلها

 private suspend fun heavyTaskFunction(){
        delay(1000)
        //Really have work goes here
    }
}

لاحظ هنا كلمة suspend اللي في الكود وديه معناها إن الميثود ديه هتوقف Coroutine اللي هسنتدعيها منه من غير ما هتوقف Thread اللي شغاله فيه، وهيكون فيه تحكم كبير يعني ممكن نعملها execution وشوية كمان نسيها ونروح لمثود تانية وشوية ونرجع نعملها Resume لو مش فاهمها دلوقتي أعتبر كأننا بنقول أن المثود ديه ممكن تاخد شوية وقت، بس خد بالك أحنا هنا مش بنحدد الThread اللي هتشتغل فيه، وبالتالي لازم تعرف إن مش اي suspend function هتكون مكانها الbackground.

عملنا كمان call لميثود اسمها delay وادينها parameter ثانية واحدة -1000 ملي ثانية- ديه جزء من Couroutines بتوقف الcoroutines لمدة معينة بس زي ما قولنا مبنقفلش الThread ونعطله كلوا، وهي تعتبر بتاخد وقت زي الوقت اللي بيعمله Network call أو إننا نجيب داتا من قاعدة بيانات موجودة على الجهاز، بس المثال للتوضيح بس وقدام هنعمل Network call كاملة

“مينفعش تستدعي Suspending function إلا من Suspending function تانية أو من Coroutine”

أيوا بالظبط، والنقطة ديه مهمة جدًا، لو عايز تستخدمها يبقا من جوا Coroutines أو من جوا Suspending fun تانية، طيب هنخرج منها المعلومات ازاي، دا هنبقا نعرفه، في الوقت الحالي احنا عايز نستخدم الدالة اللي فوق عشان تبدأ تعملنا Heavy task بتاعنا:

:CoroutineScope

ديه من الحجات التي يصعب شرحها في كلمة لكن مع مرور الوقت هتكون عارف هي بتعمل بالظبط، CoroutineScope أساسية عشان تبدأ Coroutine واحد أو مجموعة منهم بيربطهم Category واحد أنت اللي بتحدده، يعني مثلا coroutines كلهم بيعملوا network call بس واحد بيعمل POST واحد بيعمل GET وتالت بيعمل DELETE، وكمان هي اللي بتحدد الlifecycle بتاعها أمتى يبدأوا وامتى ينتهوا، والميزة الاساسية برضو انها بتتأكد أن كل Coroutine انتهى بشكل صحيح.

فيه أنواع من من الScope وفيه طرق لاستخدامهم كتيرة، بس في المقال دا بنحاول نخلي الموضوع بسيط، الشروحات الجاية هنتعمق شوية.

   CoroutineScope(Dispatchers.IO).launch {
            heavyTaskFunction()
        }

لاحظ هنا إننا استدعينا suspending fun من الCoroutine ودا مكان من الاتنين بس اللي ينفع نستعدي فيهم suspending fun، نبدأ نحلل الكود: عملنا أول CouroutineScope ودا شرحناه فوق وحطينا Dispatcher كparameter -هنشرحه حالًا- وعملنا بعديها Launch واللي معناها أبدأ الCoroutine من غير ما تعطل الThread

:Dispatcher

دا اللي بيحدد Coroutine هيبدأ في انهي ثريد وليه أربع أنواع :

  • Dispatchers.Main : دا بتاع الMain thread وبنستخدمه لو عايزين نعمل أي في View أو UI
  • Dispatchers.IO: بنستخدمه لNetwork call أو إننا نجيب داتا من قاعدة بيانات محلية
  • Dispatchers.Default: بنستخدمها لو في process بيحتاج وقت كتير مثلا أننا نفلتر list أو نعمل load للadapter
  • Dispatchers.Unconfined: دا مبيبقاش مربوط بthread معين يعني بيخلي الcoroutine تبدأ في thread اللي استدعى منها وبيتعملها resume في الthread اللي فيه suspending fun “لو مش فاهمها هي مش مهمة”

بس كدا يعتبر أحنا بدأن نعمل heavy work بالكودين اللي فوق دول من غير ما نعطل الThread أو نعمل memory overhead.

الموضوع بسيط جدًا. في الشرح الجي هنتوسع أكتر فحاول تكون فاهم اللي اتشرح هنا، ولو في أي سؤال ممكن تسأله هنا أو على الجروب.

الكاتب: Mohamed Saber

Pharmacist, Android developer and UI/UX enthusiast

(6) تعليقات

  1. هنا : “وهيكون فيه تحكم كبير يعني ممكن نعملها execution وشوية كمان نسيها ونروح لمثود تانية وشوية”
    انت تقصد ان خلال ال execution بتاع سطور ال function بي suspend وممكن يعمل حاجة تانية ويرجع يكمل من عند السطر اللي كان عامل suspend عنده صح؟

    1. أهلاً.
      أولاً بعتذر عن تأخير الرد
      قصدي على suspend fun بيكون التحكم فيها من الكورتين سهل، على عكس الfun العادية اللي لازم تتعمل كلها قبل ما يروح السطر اللي بعده،
      لما بتستدعي suspend fun من جوا كورتين هو بيشوف لو بتعمل حاجة تقيلة بيسبها تعمل وينتقل للي بعده، لو أنت عايز تخلي الكورتين يستناها بتعمل join، موجودة في الدروس الجاية

اترك ردا