في هذه المقالة سوف أتحدث عن كيفية عمل coroutines وكيفية إنشائها. النظر في التطبيق في تنفيذ متتابعة ومتوازية. دعونا نتحدث عن معالجة الأخطاء وتصحيح الأخطاء وطرق اختبار coroutine. في النهاية ، سوف ألخص وأتحدث عن الانطباعات التي بقيت بعد تطبيق هذا النهج.
تم إعداد المقالة بناءً على مواد تقريري على
MBLT DEV 2018 ، في نهاية
المنشور - رابط إلى الفيديو.
نمط ثابت
التين. 2.1ما هو الغرض من مطوري كوروتين؟ أرادوا أن تكون البرمجة غير المتزامنة بسيطة قدر الإمكان. لا يوجد شيء أسهل من تنفيذ التعليمات البرمجية "سطر بسطر" باستخدام الإنشاءات النحوية للغة: try-catch-وأخيرا ، حلقات ، بيانات شرطية ، وما إلى ذلك.
دعونا نفكر في وظيفتين. يتم تنفيذ كل منها على الخيط الخاص بها (الشكل 2.1). يتم تنفيذ الأول على مؤشر الترابط
B وإرجاع بعض النتائج
dataB ، ثم نحتاج إلى تمرير هذه النتيجة إلى الوظيفة الثانية ، والتي تأخذ
dataB كوسيطة وتعمل بالفعل على مؤشر الترابط
A. مع coroutine ، يمكننا كتابة التعليمات البرمجية لدينا كما هو موضح في الشكل. 2.1. النظر في كيفية تحقيق هذا.
الدالات
longOpOnB ، longOpOnA هي ما يسمى وظائف
الإيقاف المرحلي ، والتي يتم تحرير الخيط قبلها ، وبعد الانتهاء ، تصبح مشغولة مرة أخرى.
من أجل أن يتم تنفيذ هاتين الوظيفتين فعليًا في مؤشر ترابط مختلف مقارنةً بالوظيفة المسماة ، مع الحفاظ على نمط "ثابت" من شفرة الكتابة ، يجب علينا أن نغمرهما في سياق coroutine.
يتم ذلك عن طريق إنشاء coroutines باستخدام ما يسمى Coroutine Builder. في الشكل ، هذا
إطلاق ، لكن هناك آخرون ، على سبيل المثال ،
متزامن ،
runBlocking . سأتحدث عنها لاحقًا.
الوسيطة الأخيرة هي كتلة من التعليمات البرمجية المنفذة في سياق coroutine: استدعاء وظائف التوقف المرحلي ، مما يعني أن كل السلوك أعلاه لا يمكن تحقيقه إلا في سياق coroutine أو في وظيفة تعليق أخرى.
هناك معلمات أخرى في طريقة Coroutine Builder ، على سبيل المثال ، نوع التشغيل ، سلسلة الرسائل التي سيتم تنفيذ الكتلة ، وغيرها.
إدارة دورة الحياة
يمنحنا Coroutine Builder قيمة الإرجاع كقيمة إرجاع - فئة فرعية من فئة
Job (Fig.2.2). مع ذلك ، يمكننا إدارة دورة حياة كوروتين.
ابدأ باستخدام طريقة
start () ، وإلغاء باستخدام طريقة
Cancel () ، وانتظر المهمة لإكمالها باستخدام طريقة
join ( ) ، والاشتراك في حدث إكمال المهمة وأكثر من ذلك.
التين. 2.2تغيير التدفق
يمكنك تغيير تدفق تنفيذ coroutine عن طريق تغيير عنصر سياق coroutine المسؤول عن الجدولة. (الشكل 2.3)
على سبيل المثال ، سيتم تنفيذ corutin 1 في مؤشر ترابط
واجهة المستخدم ، بينما corutin 2 في مؤشر ترابط مأخوذة من تجمع
Dispatchers.IO .
الشكل 2.3توفر مكتبة coroutine أيضًا وظيفة إيقاف
مؤقت withContext (CoroutineContext) ، حيث يمكنك التبديل بين مؤشرات الترابط في سياق coroutine. وبالتالي ، يمكن أن يكون القفز بين الخيوط بسيطًا جدًا:
التين. 2.4.نبدأ تشغيل coroutine على مؤشر ترابط واجهة المستخدم 1 → نعرض مؤشر الحمل → التبديل إلى مؤشر ترابط العمل 2 ، ونقوم بتحرير المؤشر الرئيسي → نقوم بإجراء عملية طويلة لا يمكن تنفيذها في مؤشر ترابط واجهة المستخدم → ارجع النتيجة مرة أخرى إلى مؤشر ترابط واجهة المستخدم 3 → وتعمل بالفعل هناك مع ذلك ، تقديم البيانات المستلمة وإخفاء مؤشر التحميل.
يبدو مريح جدا حتى الآن ، والمضي قدما.
تعليق وظيفة
النظر في عمل corutin على سبيل المثال للحالة الأكثر شيوعا - العمل مع طلبات الشبكة باستخدام مكتبة Retrofit 2.
أول شيء يتعين علينا القيام به هو تحويل استدعاء
رد الاتصال إلى وظيفة
تعليق للاستفادة من ميزة coroutine:
التين. 2.5للتحكم في حالة coroutine ، توفر المكتبة وظائف النموذج
suspendXXXXCoroutine ، والتي توفر حجة تقوم بتطبيق واجهة
Continuation ، باستخدام أساليب
استئناف استئناف واستئناف يمكننا من خلالها استئناف المسار في حالة الخطأ والنجاح ، على التوالي.
بعد ذلك ، سوف نتعرف على ما يحدث عند استدعاء طريقة استئنافWithException ، أولاً ، تأكد من أننا بحاجة إلى إلغاء مكالمة طلب الشبكة بطريقة أو بأخرى.
تعليق وظيفة. استدعاء الإلغاء
لإلغاء المكالمة والإجراءات الأخرى المتعلقة بإطلاق الموارد غير المستخدمة ، عند تنفيذ وظيفة الإيقاف المرحلي ، يمكنك استخدام طريقة
الإيقاف المرحلي للإلغاء cancellableCoroutine (الشكل 2.6). هنا ، تقوم الوسيطة block بتنفيذ واجهة
CancellableContinuation بالفعل ، والتي تتيح لك إحدى الطرق الإضافية -
invokeOnCancellation - التسجيل للحصول على خطأ أو حدث إلغاء coroutine ناجح. لذلك ، من الضروري أيضًا إلغاء استدعاء الأسلوب.
التين. 2.6عرض التغييرات في واجهة المستخدم
الآن وقد تم إعداد وظيفة الإيقاف المؤقت لطلبات الشبكة ، يمكنك استخدام مكالمتها في دفق Coroutine UI كتسلسل ، بينما أثناء تنفيذ الطلب ، سيكون الدفق مجانيًا ، وسيتم استخدام دفق التعديل التحديثي للطلب.
وبالتالي ، فإننا ننفذ السلوك غير المتزامن فيما يتعلق بدفق واجهة المستخدم ، لكننا نكتبه بأسلوب ثابت (الشكل 2.6).
إذا كنت بحاجة إلى القيام بالعمل الشاق ، بعد تلقي الإجابة ، على سبيل المثال ، كتابة البيانات المستلمة إلى قاعدة البيانات ، يمكن بسهولة تنفيذ هذه الوظيفة ، كما هو موضح بالفعل ، باستخدام
withContext على مجموعة التدفقات الخلفية ومتابعة التنفيذ على واجهة المستخدم دون سطر واحد من التعليمات البرمجية.
التين. 2.7لسوء الحظ ، هذا ليس كل ما نحتاجه لتطوير التطبيق. النظر في معالجة الأخطاء.
خطأ في التعامل مع: try-catch-وأخيرا. إلغاء Coroutine: CancellationException
يعتبر الاستثناء الذي لم يتم اكتشافه داخل coroutine غير معالج وقد يؤدي إلى تعطل التطبيق. بالإضافة إلى المواقف العادية ، يتم طرح استثناء عن طريق استئناف coroutine باستخدام أسلوب استئناف
WithException على السطر المقابل من استدعاء دالة التوقف المرحلي. في هذه الحالة ، يتم استثناء الاستثناء الذي تم تمريره كوسيطة بدون تغيير. (الشكل 2.8)
التين. 2.8للمعالجة الاستثنائية ، يتوفر المحاولة المعيارية أخيرا إنشاء لغة. الآن الرمز الذي يمكنه عرض الخطأ في واجهة المستخدم يأخذ النموذج التالي:
التين. 2.9في حالة إلغاء coroutine ، والذي يمكن تحقيقه من خلال استدعاء طريقة إلغاء Job # ،
يتم طرح
CancellationException . تتم معالجة هذا الاستثناء بشكل افتراضي ولا يؤدي إلى تعطل أو عواقب سلبية أخرى.
ومع ذلك ، عند استخدام بنية
try / catch ، سيتم اكتشافها في
كتلة catch ، وستحتاج إلى حسابها في الحالات إذا كنت تريد التعامل مع المواقف "الخاطئة" فقط. على سبيل المثال ، يتم معالجة خطأ في واجهة المستخدم عندما يكون من الممكن "إلغاء" الطلبات أو تسجيل الخطأ. في الحالة الأولى ، سيتم عرض الخطأ على المستخدم ، على الرغم من أنه غير موجود بالفعل ، وفي الحالة الثانية ، سيتم تسجيل استثناء عديم الفائدة وفوضى التقارير.
لتجاهل موقف إلغاء coroutines ، تحتاج إلى تعديل الكود قليلاً:
التين. 2.10خطأ في التسجيل
النظر في استثناء استثناء تتبع مكدس.
إذا قمت بطرح استثناء مباشرة في كتلة رمز coroutine (الشكل 2.11) ، فإن تتبع المكدس يبدو أنيقًا ، مع وجود عدد قليل من المكالمات من coroutine ، فهذا يشير بشكل صحيح إلى السطر ومعلومات حول الاستثناء. في هذه الحالة ، يمكنك أن تفهم بسهولة من تتبع المكدس أين بالضبط ، وفي أي فئة وفي أي وظيفة تم طرح الاستثناء.
التين. 2.11ومع ذلك ، لا تحتوي الاستثناءات التي يتم تمريرها إلى أسلوب
restartWithException الخاص بوظائف
التوقف المرحلي ، كقاعدة عامة ، على معلومات حول coroutine الذي حدث فيه. على سبيل المثال (الشكل 2.12) ، إذا استأنفت coroutine من وظيفة الإيقاف المرحلي التي تم تنفيذها مسبقًا مع الاستثناء نفسه كما في المثال السابق ، فلن يقدم تتبع المكدس معلومات عن مكان البحث عن الخطأ على وجه التحديد.
التين. 2.12لفهم coroutine المستأنفة مع استثناء ، يمكنك استخدام
عنصر سياق
CoroutineName . (الشكل 2.13)
يتم استخدام عنصر
CoroutineName لتصحيح الأخطاء ، ويمر اسم coroutine فيه ، ويمكنك استخراجه في وظائف التوقف المرحلي ، على سبيل المثال ، استكمال رسالة الاستثناء. هذا ، على الأقل سيكون من الواضح مكان البحث عن خطأ.
لن تعمل هذه الطريقة إلا إذا تم استبعاد وظيفة الإيقاف المرحلي من هذا:
التين. 2.13خطأ في التسجيل. ExceptionHandler
لتغيير الاستثناء تسجيل الدخول ل coroutine معين ، يمكنك تعيين ExceptionHandler الخاصة بك ، والتي هي واحدة من عناصر السياق coroutine. (الشكل 2.14)
يجب أن يقوم المعالج بتطبيق واجهة
CoroutineExceptionHandler . باستخدام عامل التشغيل overridden + لسياق coroutine ، يمكنك استبدال معالج الاستثناء القياسي الخاص بك. يقع الاستثناء غير
المعالج في أسلوب
handleException ، حيث يمكنك القيام بكل ما تحتاج إليه. على سبيل المثال ، تجاهل تماما. سيحدث هذا إذا تركت المعالج فارغًا أو أضف معلوماتك الخاصة:
التين. 2.14دعونا نرى كيف قد يبدو تسجيل استثناء لدينا:
- تحتاج إلى أن تتذكر حول CancellationException ، الذي نريد أن نتجاهله.
- أضف سجلاتك الخاصة
- تذكر عن السلوك الافتراضي ، الذي يتضمن تسجيل التطبيق وإنهائه ، وإلا فإن "الاستثناء" سوف "يختفي" ببساطة ولن يكون واضحًا ما حدث.
الآن ، في حالة وجود استثناء ، سيتم إرسال قائمة تتبع مكدس إلى السجل مع المعلومات المضافة:
التين. 2.15التنفيذ الموازي. غير متزامن
النظر في عملية موازية للتعليق وظائف.
Async هو الأنسب لتنظيم نتائج متوازية من وظائف متعددة. Async ، مثل
الإطلاق - Coroutine Builder. ملائمتها هي أنه ، باستخدام طريقة
await () ، فإنها تُرجع البيانات إذا نجحت أو ألقت استثناءً حدث أثناء تنفيذ coroutine. ستنتظر طريقة الانتظار حتى يكتمل coroutine ، إذا لم تكن قد اكتملت بالفعل ، وإلا فسوف تُرجع نتيجة العمل فورًا. لاحظ أن انتظار وظيفة تعليق مؤقت ، وبالتالي لا يمكن تنفيذها خارج سياق coroutine أو وظيفة تعليق أخرى.
باستخدام المزامنة ، سيبدو الحصول على البيانات من وظيفتين بشكل متوازٍ مثل هذا:
التين. 2.16تخيل أننا نواجه مهمة الحصول على البيانات من وظيفتين بالتوازي. ثم ، تحتاج إلى الجمع بينهما والعرض. في حالة وجود خطأ ، من الضروري رسم واجهة المستخدم ، وإلغاء جميع الطلبات الحالية. وغالبا ما توجد مثل هذه الحالة في الممارسة.
في هذه الحالة ، يجب معالجة الخطأ كما يلي:
- إحضار خطأ التعامل مع داخل كل من المتزامن corutin.
- في حالة وجود خطأ ، قم بإلغاء جميع coroutines. لحسن الحظ ، لهذا من الممكن تحديد وظيفة الوالدين ، وعند الإلغاء الذي يتم إلغاء جميع أطفالها.
- لقد توصلنا إلى تطبيق إضافي لفهم ما إذا كان قد تم تحميل جميع البيانات بنجاح. على سبيل المثال ، نفترض أنه في انتظار إرجاع قيمة خالية ، حدث خطأ أثناء تلقي البيانات.
مع وضع كل هذا في الاعتبار ، أصبح تطبيق coroutine للآباء أكثر تعقيدًا. إن تنفيذ المتزامن كوروتين معقد أيضًا:
التين. 2.17هذا النهج ليس الوحيد الممكن. على سبيل المثال ، يمكنك تنفيذ التنفيذ المتوازي مع معالجة الأخطاء باستخدام
ExceptionHandler أو
SupervisorJob .
المتداخلة Coroutines
دعونا نلقي نظرة على عمل coroutine المتداخلة.
افتراضيًا ، يتم إنشاء coroutine المتداخل باستخدام نطاق خارجي ويرث السياق الخاص به. نتيجة لذلك ، يصبح coroutine المتداخلة ابنة ، والوالد الخارجي.
إذا ألغينا coroutine الخارجي ، فسيتم أيضًا إلغاء coroutines المتداخلة التي تم إنشاؤها بهذه الطريقة ، والتي تم استخدامها في المثال السابق. سيكون مفيدًا أيضًا عند مغادرة الشاشة عندما تحتاج إلى إلغاء الطلبات الحالية. بالإضافة إلى ذلك ، سينتظر ولي الأمر الأبوين دائمًا الانتهاء من الابنة.
يمكنك إنشاء coroutine مستقلة عن الخارجية باستخدام نطاق عالمي. في هذه الحالة ، عندما يتم إلغاء coroutine الخارجي ، فإن الشخص المتداخل سوف يستمر في العمل كما لو لم يحدث شيء:
التين. 2.18
يمكنك إنشاء فرع من coroutine المتداخل العالمي عن طريق استبدال عنصر السياق بمفتاح
Job بالمهمة الأصل ، أو يمكنك استخدام سياق coroutine الأصل بشكل كامل. ولكن في هذه الحالة يجدر بنا أن نتذكر أن جميع عناصر coroutine الأصل يتم الاستيلاء عليها: تجمع الخيط ، معالج الاستثناء ، وهلم جرا:
التين. 2.19من الواضح الآن أنه إذا كنت تستخدم coroutine من الخارج ، فأنت بحاجة إلى تزويدهم بالقدرة على تثبيت مثيل من الوظيفة أو سياق الوالد. ويحتاج مطورو المكتبات إلى النظر في إمكانية تثبيته كطفل ، مما يسبب الإزعاج.
نقاط التوقف
يؤثر Coroutines على عرض قيم الكائن في وضع التصحيح. إذا وضعت نقطة توقف داخل coroutine التالي على وظيفة
logData ، فعندما تشتعل ، سنرى أن كل شيء على ما يرام هنا ويتم عرض القيم بشكل صحيح:
التين. 2.20احصل الآن على
dataA باستخدام coroutine المتداخل ، وترك نقطة توقف على
logData :
التين. 2.21فشل محاولة توسيع هذا الحظر لمحاولة العثور على القيم المطلوبة. وبالتالي ، يصبح تصحيح الأخطاء في وجود وظائف التوقف المرحلي أمرًا صعبًا.
اختبار وحدة
اختبار وحدة واضحة جدا. يمكنك استخدام runoutlock Coroutine Builder
لهذا الغرض .
يعمل برنامج runBlocking على حظر الخيط حتى يتم الانتهاء من جميع أشكاله المتداخلة ، وهذا هو بالضبط ما تحتاجه للاختبار.
على سبيل المثال ، إذا كان من المعروف أنه في مكان ما داخل coroutine الطريقة يستخدم لتنفيذه ، ثم لاختبار الطريقة التي تحتاج فقط لفها في
runBlocking .
يمكن استخدام
runBlocking لاختبار وظيفة التعليق:
التين. 2.22أمثلة
أخيرًا ، أود أن أعرض بعض الأمثلة على استخدام كوروتين.
تخيل أننا بحاجة إلى تنفيذ ثلاثة استعلامات A و B و C بشكل متوازٍ ، وإظهار إتمامها وتعكس لحظة إكمال الطلبات A و B.
للقيام بذلك ، يمكنك ببساطة التفاف الاستعلام coroutines A و B في واحد مشترك واحد والعمل معها كما هو الحال مع ككل واحد:
التين. 2.23يوضح المثال التالي كيفية استخدام العادية للحلقة لتنفيذ استعلامات دورية بفاصل زمني 5 ثوان:
التين. 2.24النتائج
من السلبيات ، ألاحظ أن coroutines هي أداة شابة نسبيا ، لذلك إذا كنت ترغب في استخدامها في همز ، يجب عليك القيام بذلك بحذر. هناك صعوبات التصحيح ، وصفيحة صغيرة في تنفيذ أشياء واضحة.
بشكل عام ، من السهل جدًا استخدام coroutines ، خاصة لتنفيذ المهام غير المتزامنة غير المعقدة. على وجه الخصوص ، ويرجع ذلك إلى حقيقة أنه يمكن استخدام بنيات اللغة القياسية. Coroutines قابلة بسهولة للاختبار وحدة وكل هذا يخرج من المربع من نفس الشركة التي تطور اللغة.
تقرير الفيديو
اتضح الكثير من الحروف. بالنسبة لأولئك الذين يحبون الاستماع أكثر - فيديو من تقريري على
MBLT DEV 2018 :
مواد مفيدة حول الموضوع: