دارت 2. البرمجة غير المتزامنة: العقود الآجلة

البرمجة غير المتزامنة: العقود الآجلة


المحتويات



ما هو المهم:


  • يتم تشغيل التعليمات البرمجية في Dart في تنفيذ مؤشر ترابط واحد ( ملاحظة مؤشر ترابط - مؤشر ترابط ).
  • بسبب التعليمات البرمجية التي تأخذ (كتل) موضوع لفترة طويلة ، قد يتجمد البرنامج.
  • تمثل الكائنات Future ( futures ) نتائج العمليات غير المتزامنة - المعالجة أو الإدخال / الإخراج ، والتي سيتم إكمالها لاحقًا.
  • لتعليق التنفيذ حتى يكتمل في المستقبل ، استخدم await في الوظيفة غير المتزامنة (أو then() عند استخدام Future API).
  • لالتقاط الأخطاء ، استخدم إنشاء try-catch (أو catchError() عند استخدام Future API) في الوظيفة غير المتزامنة.
  • للمعالجة المتزامنة ، قم بإنشاء عزل (أو عامل لتطبيق الويب).

يعمل الرمز في Dart في مؤشر ترابط التنفيذ واحد. إذا كان الرمز مشغولًا بحسابات طويلة أو في انتظار عملية إدخال / إخراج ، فسيتم إيقاف البرنامج بالكامل مؤقتًا.


تسمح العمليات غير المتزامنة للبرنامج بإكمال المهام الأخرى أثناء انتظار إكمال العملية. دارت يستخدم futures لتقديم نتائج العمليات غير المتزامنة. يمكنك أيضًا استخدام المزامنة والانتظار أو واجهة برمجة تطبيقات Future للعمل مع futures .


ملاحظة


يتم تنفيذ جميع التعليمات البرمجية في سياق العزلة ، والتي تمتلك جميع الذاكرة المستخدمة من قبل رمز. لا يمكن بدء تنفيذ أكثر من كود في نفس العزل.

من أجل التنفيذ المتوازي لبنات الكود ، يمكنك فصلها إلى عزلات منفصلة. (تستخدم تطبيقات الويب العمال بدلاً من العزل.) عادةً ما يتم تشغيل كل واحدة من المعزل على جوهر المعالج الخاص بها. المعزولين لا يشاركون الذاكرة ، والطريقة الوحيدة التي يمكنهم من خلالها التفاعل هي إرسال رسائل إلى بعضهم البعض. للتعمق في الموضوع ، راجع الوثائق الخاصة بالعزلة أو العمال .

مقدمة


دعونا نلقي نظرة على مثال للرمز الذي يمكن أن "يجمد" تنفيذ البرنامج:


 // Synchronous code void printDailyNewsDigest() { var newsDigest = gatherNewsReports(); // Can take a while. print(newsDigest); } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } 

يقوم برنامجنا بقراءة الأخبار من الملف لهذا اليوم ، ويعرضها ، ثم يعرض المعلومات التي لا تزال تهم المستخدم:


 <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 ، يحدث شيئان:


  1. قوائم الانتظار الدالة للتنفيذ وإرجاع كائن Future غير كامل.
  2. في وقت لاحق ، عند اكتمال العملية ، ينتهي future بقيمة أو خطأ.

لكتابة رمز يعتمد على future ، لديك خياران:


  • استخدام async - await
  • استخدم واجهة برمجة تطبيقات Future

متزامن - تنتظر


تعتبر الكلمات الرئيسية غير async جزءًا من دعم Dart's async . إنها تتيح لك كتابة تعليمات برمجية غير متزامنة تشبه التعليمات البرمجية المتزامنة ولا تستخدم واجهة برمجة تطبيقات Future . دالة غير متزامنة هي وظيفة بها الكلمة الأساسية غير async أمام async . الكلمة الرئيسية await يعمل فقط في وظائف غير متزامنة.


ملاحظة: في Dart 1.x ، تؤدي الوظائف غير المتزامنة إلى تأخير التنفيذ على الفور. في Dart 2 ، بدلاً من التوقف الفوري ، يتم تنفيذ الوظائف غير المتزامنة بشكل متزامن حتى يتم await أو return الأولى.

التعليمات البرمجية التالية تحاكي قراءة الأخبار من ملف باستخدام async - await . افتح DartPad مع التطبيق ، ثم ابدأ ، وانقر فوق CONSOLE لرؤية النتيجة.


رمز المثال
 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:async'; Future<void> printDailyNewsDigest() async { var newsDigest = await gatherNewsReports(); print(newsDigest); } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } printWinningLotteryNumbers() { print('Winning lotto numbers: [23, 63, 87, 26, 2]'); } printWeatherForecast() { print("Tomorrow's forecast: 70F, sunny."); } printBaseballScore() { print('Baseball score: Red Sox 10, Yankees 0'); } const news = '<gathered news goes here>'; const oneSecond = Duration(seconds: 1); // Imagine that this function is more complex and slow. :) Future<String> gatherNewsReports() => Future.delayed(oneSecond, () => news); // Alternatively, you can get news from a server using features // from either dart:io or dart:html. For example: // // import 'dart:html'; // // Future<String> gatherNewsReportsFromServer() => HttpRequest.getString( // 'https://www.dartlang.org/f/dailyNewsDigest.txt', // ); 

لاحظ أننا ندعو printDailyNewsDigest() ، ولكن تتم طباعة الخبر أخيرًا ، حتى إذا كان الملف يحتوي على سطر واحد فقط. وذلك لأن الشفرة التي تقرأ وتطبع الملف تعمل بشكل غير متزامن.


في هذا المثال ، تقوم printDailyNewsDigest() بإجراء مكالمة إلى gatherNewsReports() ، والتي لا يتم حظرها. استدعاء الأسلوب gatherNewsReports() المهمة ، لكنه لا يوقف تنفيذ بقية التعليمات البرمجية. يعرض البرنامج أرقام اليانصيب والتنبؤ بنتيجة لعبة البيسبول. يقوم البرنامج بطباعة الأخبار بعد اكتمال مجموعة gatherNewsReports() . إذا gatherNewsReports() وقت إتمام أعماله gatherNewsReports() ، فلن يحدث شيء سيء: يمكن للمستخدم قراءة أشياء أخرى قبل طباعة ملخص الأخبار اليومي.


إيلاء الاهتمام لأنواع العودة. نوع الإرجاع gatherNewsReports() هو Future<String> ، مما يعني أنها تُرجع future ينتهي بقيمة سلسلة. printDailyNewsDigest() ، التي لا تُرجع قيمة ، لها نوع إرجاع Future<void> .


يوضح المخطط التالي خطوات تنفيذ التعليمات البرمجية.



  1. يبدأ التطبيق للتشغيل.
  2. printDailyNewsDigest() الوظيفة main() الدالة غير المتزامنة printDailyNewsDigest() ، والتي تبدأ في العمل بشكل متزامن.
  3. printDailyNewsDigest() يستخدم await استدعاء gatherNewsReports() التي تبدأ في التشغيل.
  4. gatherNewsReports() بإرجاع future غير مكتمل (مثيل لـ Future<String> ).
  5. نظرًا لأن printDailyNewsDigest() هي وظيفة غير متزامنة وتتوقع قيمة ، فهي توقف التنفيذ وتُرجع future غير المكتمل (في هذه الحالة ، Future<void> ) إلى وظيفة الاستدعاء main () .
  6. يتم تنفيذ بقية وظائف الإخراج. نظرًا لأنها متزامنة ، يتم تنفيذ كل وظيفة بالكامل قبل الانتقال إلى التالي. على سبيل المثال ، سيتم عرض جميع أرقام اليانصيب الفائزة قبل توقعات الطقس.
  7. بعد الانتهاء من main() يمكن استئناف وظائف غير متزامنة التنفيذ. أولاً ، نحصل على future بأخبار عن الانتهاء من gatherNewsReports() . ثم printDailyNewsDigest() التنفيذ ، ويعرض الأخبار.
  8. في نهاية تنفيذ printDailyNewsDigest() ، future استلامه أصلاً وإنهاء التطبيق.

لاحظ أن الوظيفة غير المتزامنة تبدأ على الفور (بشكل متزامن). تقوم الدالة بإيقاف التنفيذ وإرجاع future غير منتهي عند حدوث التواجد الأول لأي مما يلي:


  • أول تعبير await (بعد الحصول على الدالة في future غير الكامل من هذا التعبير).
  • أي return في وظيفة.
  • نهاية الجسم وظيفة.

خطأ في التعامل


من المرجح أنك ترغب في "التقاط" خطأ في تنفيذ الوظيفة التي تُرجع future . في الوظائف غير المتزامنة ، يمكنك معالجة الأخطاء باستخدام try-catch :


 Future<void> printDailyNewsDigest() async { try { var newsDigest = await gatherNewsReports(); print(newsDigest); } catch (e) { // Handle error... } } 

تتصرف try-catch ذات الشفرة غير المتزامنة كما هي الحال في التعليمات البرمجية المتزامنة: إذا كانت الشفرة الموجودة في block try استثناءً ، catch تنفيذ الرمز داخل catch .


التنفيذ المتسلسل


يمكنك استخدام تعبيرات await متعددة لضمان اكتمال كل عبارة قبل تنفيذ ما يلي:


 // Sequential processing using async and await. main() async { await expensiveA(); await expensiveB(); doSomethingWith(await expensiveC()); } 

لا يتم تنفيذ الدالة costB expensiveA() حتى اكتمال expensiveA() وهكذا.


API المستقبل


قبل أن تتم إضافة المزامنة والانتظار في Dart 1.9 ، كان عليك استخدام واجهة برمجة تطبيقات Future . لا يزال بإمكانك رؤية استخدام واجهة برمجة تطبيقات Future في التعليمات البرمجية القديمة وفي الكود الذي يحتاج إلى وظائف أكثر من async–await هذا العرض.


لكتابة تعليمة برمجية غير متزامنة باستخدام واجهة برمجة تطبيقات Future ، استخدم الطريقة then() لتسجيل رد الاتصال. رد الاتصال هذا سيعمل عند اكتمال future .


يحاكي التعليمة البرمجية التالية قراءة الأخبار من ملف باستخدام Future API. افتح DartPad مع التطبيق ، ثم ابدأ ، وانقر فوق CONSOLE لرؤية النتيجة.


رمز المثال
 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:async'; Future<void> printDailyNewsDigest() { final future = gatherNewsReports(); return future.then(print); // You don't *have* to return the future here. // But if you don't, callers can't await it. } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } printWinningLotteryNumbers() { print('Winning lotto numbers: [23, 63, 87, 26, 2]'); } printWeatherForecast() { print("Tomorrow's forecast: 70F, sunny."); } printBaseballScore() { print('Baseball score: Red Sox 10, Yankees 0'); } const news = '<gathered news goes here>'; const oneSecond = Duration(seconds: 1); // Imagine that this function is more complex and slow. :) Future<String> gatherNewsReports() => Future.delayed(oneSecond, () => news); // Alternatively, you can get news from a server using features // from either dart:io or dart:html. For example: // // import 'dart:html'; // // Future<String> gatherNewsReportsFromServer() => HttpRequest.getString( // 'https://www.dartlang.org/f/dailyNewsDigest.txt', // ); 

لاحظ أننا ندعو printDailyNewsDigest() ، ولكن تتم طباعة الخبر أخيرًا ، حتى إذا كان الملف يحتوي على سطر واحد فقط. وذلك لأن الشفرة التي تقرأ وتطبع الملف تعمل بشكل غير متزامن.


يعمل هذا التطبيق على النحو التالي:


  1. يبدأ التطبيق للتشغيل.
  2. تقوم الوظيفة الرئيسية باستدعاء printDailyNewsDigest() ، والتي لا تُرجع النتيجة على الفور ، ولكنها تستدعي أولاً gatherNewsReports() .
  3. يبدأ gatherNewsReports() قراءة الأخبار وإرجاع future .
  4. يستخدم printDailyNewsDigest() then() لتسجيل رد اتصال سيستغرق كمعلمة القيمة التي تم الحصول عليها في نهاية future . إرجاع المكالمة then() future جديدًا ، والذي ينتهي بالقيمة التي يتم إرجاعها بواسطة رد الاتصال من then() .
  5. يتم تنفيذ بقية وظائف الإخراج. نظرًا لأنها متزامنة ، يتم تنفيذ كل وظيفة بالكامل قبل الانتقال إلى التالي. على سبيل المثال ، سيتم عرض جميع أرقام اليانصيب الفائزة قبل توقعات الطقس.
  6. عندما يتم تلقي جميع الأخبار ، فإن future إرجاعه بواسطة الدالة gatherNewsReports() ينتهي بسلسلة تحتوي على الأخبار التي تم جمعها.
  7. يتم تنفيذ التعليمات البرمجية المحددة في then() في printDailyNewsDigest() الأخبار.
  8. التطبيق يغلق.

ملاحظة: في printDailyNewsDigest() ، يكون رمز future.then(print) مكافئًا لما يلي: future.then((newsDigest) => print(newsDigest)) .

أيضًا ، يمكن للكود الموجود في الداخل then() استخدام الأقواس المعقوفة:


 Future<void> printDailyNewsDigest() { final future = gatherNewsReports(); return future.then((newsDigest) { print(newsDigest); // Do something else... }); } 

يجب عليك تحديد وسيطة رد الاتصال في then() ، حتى إذا كان future من النوع Future<void> . حسب الاتفاقية ، يتم تعريف وسيطة غير مستخدمة من خلال _ (تسطير أسفل السطر).


 final future = printDailyNewsDigest(); return future.then((_) { // Code that doesn't use the `_` parameter... print('All reports printed.'); }); 

خطأ في التعامل


باستخدام Future API ، يمكنك catchError() الخطأ باستخدام catchError() :


 Future<void> printDailyNewsDigest() => gatherNewsReports().then(print).catchError(handleError); 

إذا كانت الأخبار غير قابلة للقراءة ، فسيتم تنفيذ الرمز أعلاه على النحو التالي:


  1. فشل future إرجاع بواسطة gatherNewsReports() .
  2. future إرجاع بحلول then() فشل ، لا يسمى print() .
  3. 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. البرمجة غير المتزامنة: تدفقات البيانات

Source: https://habr.com/ru/post/ar442282/


All Articles