البرمجة غير المتزامنة: العقود الآجلة
المحتويات
ما هو المهم:
- يتم تشغيل التعليمات البرمجية في Dart في تنفيذ مؤشر ترابط واحد ( ملاحظة مؤشر ترابط - مؤشر ترابط ).
- بسبب التعليمات البرمجية التي تأخذ (كتل) موضوع لفترة طويلة ، قد يتجمد البرنامج.
- تمثل الكائنات
Future
( futures
) نتائج العمليات غير المتزامنة - المعالجة أو الإدخال / الإخراج ، والتي سيتم إكمالها لاحقًا. - لتعليق التنفيذ حتى يكتمل في المستقبل ، استخدم
await
في الوظيفة غير المتزامنة (أو then()
عند استخدام Future
API). - لالتقاط الأخطاء ، استخدم إنشاء
try-catch
(أو catchError()
عند استخدام Future
API) في الوظيفة غير المتزامنة. - للمعالجة المتزامنة ، قم بإنشاء عزل (أو عامل لتطبيق الويب).
يعمل الرمز في Dart في مؤشر ترابط التنفيذ واحد. إذا كان الرمز مشغولًا بحسابات طويلة أو في انتظار عملية إدخال / إخراج ، فسيتم إيقاف البرنامج بالكامل مؤقتًا.
تسمح العمليات غير المتزامنة للبرنامج بإكمال المهام الأخرى أثناء انتظار إكمال العملية. دارت يستخدم futures
لتقديم نتائج العمليات غير المتزامنة. يمكنك أيضًا استخدام المزامنة والانتظار أو واجهة برمجة تطبيقات Future للعمل مع futures
.
ملاحظة
يتم تنفيذ جميع التعليمات البرمجية في سياق العزلة ، والتي تمتلك جميع الذاكرة المستخدمة من قبل رمز. لا يمكن بدء تنفيذ أكثر من كود في نفس العزل.
من أجل التنفيذ المتوازي لبنات الكود ، يمكنك فصلها إلى عزلات منفصلة. (تستخدم تطبيقات الويب العمال بدلاً من العزل.) عادةً ما يتم تشغيل كل واحدة من المعزل على جوهر المعالج الخاص بها. المعزولين لا يشاركون الذاكرة ، والطريقة الوحيدة التي يمكنهم من خلالها التفاعل هي إرسال رسائل إلى بعضهم البعض. للتعمق في الموضوع ، راجع الوثائق الخاصة بالعزلة أو العمال .
مقدمة
دعونا نلقي نظرة على مثال للرمز الذي يمكن أن "يجمد" تنفيذ البرنامج:
يقوم برنامجنا بقراءة الأخبار من الملف لهذا اليوم ، ويعرضها ، ثم يعرض المعلومات التي لا تزال تهم المستخدم:
<gathered news goes here> Winning lotto numbers: [23, 63, 87, 26, 2] Tomorrow's forecast: 70F, sunny. Baseball score: Red Sox 10, Yankees 0
في هذا المثال ، المشكلة هي أن جميع العمليات بعد استدعاء gatherNewsReports()
ستنتظر حتى تقوم gatherNewsReports()
بإرجاع محتويات الملف ، بغض النظر عن المدة التي يستغرقها. إذا استغرقت قراءة الملف وقتًا طويلاً ، فسيضطر المستخدم إلى انتظار نتائج اليانصيب وتوقعات الطقس والفائز في لعبة حديثة.
للحفاظ على استجابة التطبيق ، يستخدم مؤلفو Dart نموذجًا غير متزامن لتحديد الوظائف التي تؤدي عملاً باهظ التكلفة. هذه الوظائف ترجع قيمتها باستخدام futures
.
ما هو المستقبل؟
future
- هو مثيل لفئة Future <T> ، وهي عملية غير متزامنة تقوم بإرجاع نتيجة من النوع T. إذا لم يتم استخدام نتيجة العملية ، فسيتم الإشارة إلى النوع في Future<void>
خلال Future<void>
. عند استدعاء دالة تُرجع future
، يحدث شيئان:
- قوائم الانتظار الدالة للتنفيذ وإرجاع كائن
Future
غير كامل. - في وقت لاحق ، عند اكتمال العملية ، ينتهي
future
بقيمة أو خطأ.
لكتابة رمز يعتمد على future
، لديك خياران:
- استخدام
async
- await
- استخدم واجهة برمجة تطبيقات
Future
متزامن - تنتظر
تعتبر الكلمات الرئيسية غير async
جزءًا من دعم Dart's async
. إنها تتيح لك كتابة تعليمات برمجية غير متزامنة تشبه التعليمات البرمجية المتزامنة ولا تستخدم واجهة برمجة تطبيقات Future
. دالة غير متزامنة هي وظيفة بها الكلمة الأساسية غير async
أمام async
. الكلمة الرئيسية await
يعمل فقط في وظائف غير متزامنة.
ملاحظة: في Dart 1.x ، تؤدي الوظائف غير المتزامنة إلى تأخير التنفيذ على الفور. في Dart 2 ، بدلاً من التوقف الفوري ، يتم تنفيذ الوظائف غير المتزامنة بشكل متزامن حتى يتم await
أو return
الأولى.
التعليمات البرمجية التالية تحاكي قراءة الأخبار من ملف باستخدام async
- await
. افتح DartPad مع التطبيق ، ثم ابدأ ، وانقر فوق CONSOLE لرؤية النتيجة.
لاحظ أننا ندعو printDailyNewsDigest()
، ولكن تتم طباعة الخبر أخيرًا ، حتى إذا كان الملف يحتوي على سطر واحد فقط. وذلك لأن الشفرة التي تقرأ وتطبع الملف تعمل بشكل غير متزامن.
في هذا المثال ، تقوم printDailyNewsDigest()
بإجراء مكالمة إلى gatherNewsReports()
، والتي لا يتم حظرها. استدعاء الأسلوب gatherNewsReports()
المهمة ، لكنه لا يوقف تنفيذ بقية التعليمات البرمجية. يعرض البرنامج أرقام اليانصيب والتنبؤ بنتيجة لعبة البيسبول. يقوم البرنامج بطباعة الأخبار بعد اكتمال مجموعة gatherNewsReports()
. إذا gatherNewsReports()
وقت إتمام أعماله gatherNewsReports()
، فلن يحدث شيء سيء: يمكن للمستخدم قراءة أشياء أخرى قبل طباعة ملخص الأخبار اليومي.
إيلاء الاهتمام لأنواع العودة. نوع الإرجاع gatherNewsReports()
هو Future<String>
، مما يعني أنها تُرجع future
ينتهي بقيمة سلسلة. printDailyNewsDigest()
، التي لا تُرجع قيمة ، لها نوع إرجاع Future<void>
.
يوضح المخطط التالي خطوات تنفيذ التعليمات البرمجية.

- يبدأ التطبيق للتشغيل.
printDailyNewsDigest()
الوظيفة main()
الدالة غير المتزامنة printDailyNewsDigest()
، والتي تبدأ في العمل بشكل متزامن.printDailyNewsDigest()
يستخدم await
استدعاء gatherNewsReports()
التي تبدأ في التشغيل.gatherNewsReports()
بإرجاع future
غير مكتمل (مثيل لـ Future<String>
).- نظرًا لأن
printDailyNewsDigest()
هي وظيفة غير متزامنة وتتوقع قيمة ، فهي توقف التنفيذ وتُرجع future
غير المكتمل (في هذه الحالة ، Future<void>
) إلى وظيفة الاستدعاء main ()
. - يتم تنفيذ بقية وظائف الإخراج. نظرًا لأنها متزامنة ، يتم تنفيذ كل وظيفة بالكامل قبل الانتقال إلى التالي. على سبيل المثال ، سيتم عرض جميع أرقام اليانصيب الفائزة قبل توقعات الطقس.
- بعد الانتهاء من
main()
يمكن استئناف وظائف غير متزامنة التنفيذ. أولاً ، نحصل على future
بأخبار عن الانتهاء من gatherNewsReports()
. ثم printDailyNewsDigest()
التنفيذ ، ويعرض الأخبار. - في نهاية تنفيذ
printDailyNewsDigest()
، future
استلامه أصلاً وإنهاء التطبيق.
لاحظ أن الوظيفة غير المتزامنة تبدأ على الفور (بشكل متزامن). تقوم الدالة بإيقاف التنفيذ وإرجاع future
غير منتهي عند حدوث التواجد الأول لأي مما يلي:
- أول تعبير
await
(بعد الحصول على الدالة في future
غير الكامل من هذا التعبير). - أي
return
في وظيفة. - نهاية الجسم وظيفة.
خطأ في التعامل
من المرجح أنك ترغب في "التقاط" خطأ في تنفيذ الوظيفة التي تُرجع future
. في الوظائف غير المتزامنة ، يمكنك معالجة الأخطاء باستخدام try-catch
:
Future<void> printDailyNewsDigest() async { try { var newsDigest = await gatherNewsReports(); print(newsDigest); } catch (e) {
تتصرف try-catch
ذات الشفرة غير المتزامنة كما هي الحال في التعليمات البرمجية المتزامنة: إذا كانت الشفرة الموجودة في block try
استثناءً ، catch
تنفيذ الرمز داخل catch
.
التنفيذ المتسلسل
يمكنك استخدام تعبيرات await
متعددة لضمان اكتمال كل عبارة قبل تنفيذ ما يلي:
لا يتم تنفيذ الدالة costB expensiveA()
حتى اكتمال expensiveA()
وهكذا.
API المستقبل
قبل أن تتم إضافة المزامنة والانتظار في Dart 1.9 ، كان عليك استخدام واجهة برمجة تطبيقات Future
. لا يزال بإمكانك رؤية استخدام واجهة برمجة تطبيقات Future
في التعليمات البرمجية القديمة وفي الكود الذي يحتاج إلى وظائف أكثر من async–await
هذا العرض.
لكتابة تعليمة برمجية غير متزامنة باستخدام واجهة برمجة تطبيقات Future
، استخدم الطريقة then()
لتسجيل رد الاتصال. رد الاتصال هذا سيعمل عند اكتمال future
.
يحاكي التعليمة البرمجية التالية قراءة الأخبار من ملف باستخدام Future
API. افتح DartPad مع التطبيق ، ثم ابدأ ، وانقر فوق CONSOLE لرؤية النتيجة.
لاحظ أننا ندعو printDailyNewsDigest()
، ولكن تتم طباعة الخبر أخيرًا ، حتى إذا كان الملف يحتوي على سطر واحد فقط. وذلك لأن الشفرة التي تقرأ وتطبع الملف تعمل بشكل غير متزامن.
يعمل هذا التطبيق على النحو التالي:
- يبدأ التطبيق للتشغيل.
- تقوم الوظيفة الرئيسية باستدعاء
printDailyNewsDigest()
، والتي لا تُرجع النتيجة على الفور ، ولكنها تستدعي أولاً gatherNewsReports()
. - يبدأ
gatherNewsReports()
قراءة الأخبار وإرجاع future
. - يستخدم
printDailyNewsDigest()
then()
لتسجيل رد اتصال سيستغرق كمعلمة القيمة التي تم الحصول عليها في نهاية future
. إرجاع المكالمة then()
future
جديدًا ، والذي ينتهي بالقيمة التي يتم إرجاعها بواسطة رد الاتصال من then()
. - يتم تنفيذ بقية وظائف الإخراج. نظرًا لأنها متزامنة ، يتم تنفيذ كل وظيفة بالكامل قبل الانتقال إلى التالي. على سبيل المثال ، سيتم عرض جميع أرقام اليانصيب الفائزة قبل توقعات الطقس.
- عندما يتم تلقي جميع الأخبار ، فإن
future
إرجاعه بواسطة الدالة gatherNewsReports()
ينتهي بسلسلة تحتوي على الأخبار التي تم جمعها. - يتم تنفيذ التعليمات البرمجية المحددة في
then()
في printDailyNewsDigest()
الأخبار. - التطبيق يغلق.
ملاحظة: في printDailyNewsDigest()
، يكون رمز future.then(print)
مكافئًا لما يلي: future.then((newsDigest) => print(newsDigest))
.
أيضًا ، يمكن للكود الموجود في الداخل then()
استخدام الأقواس المعقوفة:
Future<void> printDailyNewsDigest() { final future = gatherNewsReports(); return future.then((newsDigest) { print(newsDigest);
يجب عليك تحديد وسيطة رد الاتصال في then()
، حتى إذا كان future
من النوع Future<void>
. حسب الاتفاقية ، يتم تعريف وسيطة غير مستخدمة من خلال _
(تسطير أسفل السطر).
final future = printDailyNewsDigest(); return future.then((_) {
خطأ في التعامل
باستخدام Future
API ، يمكنك catchError()
الخطأ باستخدام catchError()
:
Future<void> printDailyNewsDigest() => gatherNewsReports().then(print).catchError(handleError);
إذا كانت الأخبار غير قابلة للقراءة ، فسيتم تنفيذ الرمز أعلاه على النحو التالي:
- فشل
future
إرجاع بواسطة gatherNewsReports()
. future
إرجاع بحلول then()
فشل ، لا يسمى print()
.catchError()
في catchError()
( handleError()
) يمسك الخطأ ، future
إرجاعه بواسطة catchError()
يكمل بشكل طبيعي ، والخطأ لا ينتشر بشكل أكبر.
then()
سلسلة - catchError()
هو نمط شائع عند استخدام Future
API. اعتبر هذا الزوج بمثابة مكافئ try-catch
في واجهة برمجة تطبيقات Future
.
مثل ثم () ، تقوم catchError () بإرجاع future
جديد ينتهي بقيمة إرجاع رد الاتصال. للتعمق في الموضوع ، اقرأ العقود المستقبلية ومعالجة الأخطاء .
استدعاء وظائف متعددة تعود future
دعنا نأخذ في الاعتبار ثلاث وظائف: expensiveA()
، expensiveB()
، expensiveC()
، والتي ترجع future
. يمكنك الاتصال بهم بالتسلسل (تبدأ وظيفة واحدة بعد اكتمال الوظيفة السابقة) ، أو يمكنك تشغيلها جميعًا في نفس الوقت والقيام بشيء ما بمجرد إرجاع جميع القيم. واجهة Future مرنة بما يكفي لتنفيذ كل من حالات الاستخدام.
سلسلة من المكالمات الدالة باستخدام then()
عندما يجب تنفيذ الوظائف التي تعود إلى future
بالترتيب ، استخدم السلسلة من then()
:
expensiveA() .then((aValue) => expensiveB()) .then((bValue) => expensiveC()) .then((cValue) => doSomethingWith(cValue));
تعمل إرفاق عمليات الاسترجاعات أيضًا ، لكن من الصعب قراءتها. ( لاحظ http://callbackhell.com/ )
في انتظار استكمال العديد من futures
باستخدام Future.wait()
إذا كان ترتيب تنفيذ الوظائف غير مهم ، يمكنك استخدام Future.wait()
. عند تحديد قائمة futures
لمعلمات دالة Future.wait () ، فإنها تُرجع future
على الفور. لن ينتهي هذا future
حتى futures
جميع futures
المحددة. سينتهي هذا future
بقائمة نتائج جميع futures
المشار إليها.
Future.wait([expensiveA(), expensiveB(), expensiveC()]) .then((List responses) => chooseBestResponse(responses, moreInfo)) .catchError(handleError);
إذا فشل استدعاء أي من الوظائف ، فإن future
إرجاعه بواسطة Future.wait()
يفشل أيضًا. استخدم catchError()
لالتقاط هذا الخطأ.
ماذا تقرأ؟
دارت 2. البرمجة غير المتزامنة: تدفقات البيانات