جزيرة كوتلنالنصوص السابقة في هذه السلسلة: حول AsyncTask ، حول لوادر ، حول التنفيذيين و EventBus ، حول RxJava .لقد حان هذه الساعة. هذه هي المقالة التي كُتبت من أجلها السلسلة بأكملها: شرح لكيفية عمل النهج الجديد "تحت الغطاء". إذا كنت لا تعرف حتى الآن كيفية استخدامه ، فإليك بعض الروابط المفيدة لتبدأ:
وبعد إتقانك للكوروتينات ، قد تتساءل عما سمح لكوتلن بتوفير هذه الفرصة وكيف تعمل. يرجى ملاحظة أننا سنركز هنا فقط على مرحلة التجميع: يمكنك كتابة مقال منفصل حول التنفيذ.
أول شيء يجب أن نفهمه هو أنه في الجسم ، في الواقع لا يوجد كوروتينات. يقوم المحول البرمجي بتحويل الوظيفة مع مُعدِّل التوقف المرحلي إلى دالة ذات معلمة
Continuation . تحتوي هذه الواجهة على طريقتين:
abstract fun resume(value: T) abstract fun resumeWithException(exception: Throwable)
النوع T هو نوع الإرجاع لوظيفة التوقف الأصلية الخاصة بك. وإليك ما يحدث بالفعل: يتم تنفيذ هذه الوظيفة في خيط معين (الصبر ، نصل أيضًا إلى هذا) ، ويتم تمرير النتيجة إلى وظيفة السيرة الذاتية لهذا الاستمرارية ، والتي تم في سياقها استدعاء وظيفة الإيقاف المؤقت. إذا لم تستلم الوظيفة النتيجة وألقت استثناءً ، فسيتم طرح استئنافWithException ، مما أدى إلى حدوث خطأ في رمز الاستدعاء.
حسنًا ، ولكن من أين أتت المتابعة؟ بالطبع ، من باني Corutin! دعونا نلقي نظرة على الرمز الذي ينشئ أي كوروتين ، على سبيل المثال ، إطلاق:
public actual fun launch( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context, parent) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }
هنا ، يُنشئ المُنشئ coroutine - نسخة من فئة AbstractCoroutine ، والتي بدورها تنفذ واجهة المتابعة. تنتمي طريقة البدء إلى واجهة الوظيفة. لكن العثور على تعريف طريقة البدء أمر صعب للغاية. لكن يمكننا أن نأتي إلى هنا من الجانب الآخر. لاحظ القارئ اليقظ بالفعل أن الوسيطة الأولى لوظيفة الإطلاق هي CoroutineContext ، ويتم تعيينها افتراضيًا على DefaultDispatcher. المرسلون عبارة عن فصول تتحكم في تنفيذ الجينات ، لذا فهي مهمة بالتأكيد لفهم ما يحدث. دعونا نلقي نظرة على إعلان DefaultDispatcher:
public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool
لذا ، في الواقع ، هذا هو CommonPool ، على الرغم من أن أحواض جافا تخبرنا أن هذا يمكن أن يتغير. ما هو CommonPool؟
هذا هو مدير coroutine باستخدام
ForkJoinPool كتطبيق ExecutorService. نعم ، إنه: في النهاية ، جميع روتينات لامدا الخاصة بك هي فقط Runnable ، محاصرة في Executor مع مجموعة من التحولات الصعبة. لكن الشيطان ، كما هو الحال دائمًا ، يكمن في التفاصيل.
شوكة؟ أو الانضمام؟إذا حكمنا من خلال نتائج الاستبيان على تويتر ، فأنا بحاجة إلى شرح موجز عن ماهية FJP :)
بادئ ذي بدء ، ForkJoinPool هو منفذ تنفيذي حديث تم إنشاؤه للاستخدام مع تدفقات Java 8. المتوازية. كانت المهمة الأصلية هي التوازي الفعال عند العمل مع Stream API ، مما يعني بشكل أساسي تقسيم التدفقات لمعالجة جزء من البيانات ثم دمجها عند معالجة جميع البيانات. لتبسيط الأمر ، تخيل أن لديك الرمز التالي:
IntStream .range(1, 1_000_000) .parallel() .sum()
لن يتم حساب مقدار هذا التدفق في دفق واحد ، بدلاً من ذلك ، ستقوم ForkJoinPool بتقسيم النطاق بشكل متكرر إلى أجزاء (أولاً إلى جزأين من 500000 ، ثم كل واحد منهم إلى 250.000 ، وما إلى ذلك) ، وحساب مجموع كل جزء ، ودمج النتائج في واحد المبلغ. فيما يلي تصور لهذه العملية:
يتم تقسيم سلاسل الرسائل لمهام مختلفة ودمجها مرة أخرى بعد الانتهاءتعتمد فعالية FJP على خوارزمية "سرقة الوظيفة": عندما ينفد مؤشر ترابط معين من المهام ، فإنه ينتقل إلى قوائم انتظار مؤشرات الترابط الأخرى ويسرق مهامهم. لفهم أفضل ، يمكنك الاطلاع على
تقرير Alexei Shipilev أو مشاهدة
عرض تقديمي .
حسنًا ، لقد أدركنا ما تفعله corotines لدينا! لكن كيف ينتهي بهم المطاف هناك؟
يحدث هذا داخل طريقة الإرسال CommonPool #:
_pool.execute(timeSource.trackTask(block))
يتم استدعاء طريقة الإرسال من أسلوب السيرة الذاتية (القيمة: T) في DispatchedContinuation. تبدو مألوفة! نتذكر أن المتابعة هي واجهة يتم تنفيذها في AbstractCoroutine. ولكن كيف ترتبط؟
الحيلة داخل فئة CoroutineDispatcher. ينفذ واجهة ContinuationInterceptor على النحو التالي:
public actual override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation)
ترى؟ يمكنك توفير كتلة بسيطة ل corutin البناء. لا تحتاج إلى تنفيذ أي واجهات لا تريد معرفة شيء عنها. تهتم مكتبة Coroutine بكل هذا. هي كذلك
يعترض التنفيذ ، ويستبدل المتابعة بـ DispatchedContinuation ، ويرسلها إلى المنفذ ، مما يضمن التنفيذ الأكثر فعالية لشفرتك.
الآن الشيء الوحيد الذي نحتاج إلى التعامل معه هو كيفية استدعاء الإرسال من طريقة البداية. دعونا نملأ هذه الفجوة. يتم استدعاء طريقة الاستئناف من startCoroutine في وظيفة التمديد للكتلة:
public fun <R, T> (suspend R.() -> T).startCoroutine( receiver: R, completion: Continuation<T> ) { createCoroutineUnchecked(receiver, completion).resume(Unit) }
وبدء Cororine ، بدوره ، يتم استدعاؤه بواسطة عامل التشغيل "()" في تعداد CoroutineStart. يقبل المنشئ الخاص بك كمعلمة ثانية ، والافتراضي هو CoroutineStart.DEFAULT. هذا كل شيء!
هذا هو السبب في أنني معجب بنهج كوروتين: فهو ليس فقط بناءًا مذهلاً ، ولكنه أيضًا تطبيق رائع.
وبالنسبة لأولئك الذين قرأوا حتى النهاية ، فإنهم يحصلون على حصري: مقطع فيديو لتقريري "عازف الكمان ليس هناك حاجة: نرفض RxJava لصالح كوروتين في Kotlin" من مؤتمر Mobius . استمتع :)