July 25, 2023

Flutter isolates - bilishingiz kerak bo'lgan hamma narsa

"Izolyatsiya" so'zi siz uchun yangi bo'lmasligi kerak - pandemiya boshlanganidan beri u mashhur so'zga aylandi. Ehtimol, siz bu atamani Flutterga nisbatan ishlatilganini ham eshitgandirsiz, ammo "isolates" o'zi nima? Dart kodingizni qanday "run" qiladi? Isolatesni qanday amalga oshirasiz? Bu savollarning barchasiga ushbu maqolada javob beriladi.

Agar siz Flutterda yangi bo'lsangiz, bu maqola siz uchun, chunki men ko'plab tushunchalarni soddalashtirishga harakat qildim. Ammo, agar siz tajribali dasturchi bo'lsangiz, ushbu maqola ham foydali bo'lishi mumkin. Flutter izolatlari kontseptsiyasini tushuntirishga yordam berish uchun biz kofe kioskining analogiyasidan foydalanamiz .

Dartda kodning bajarilishi

Dart - bu bir oqimli til (single thread). Bu nimani anglatishini tushunish uchun birinchi navbatda oqimlar nima ekanligini tushunishimiz kerak.

Oqim (thread) nima?

Oqim - bu jarayonning birligi. Bu asosiy jarayondan mustaqil ravishda rejalashtirilishi va bajarilishi uchun mo'ljallangan kichik ko'rsatmalar to'plami.

Hisoblashda jarayon atamasi bajarilayotgan dasturning holatini bildiradi.

Endi sizni bir narsa qiziqtirayotgan bo‘lishi mumkin: Agar Dart bitta oqimli til bo‘lsa, Flutter-dagi asinxron vazifalar (masalan, HTTP so'rovi orqali ma’lumotlarni olish) ilovaning boshqa faoliyatiga to‘sqinlik qilmasdan qanday qilib optimal tarzda bajarilishi mumkin? Xo'sh, bu ikki xil tushunchaga olib keladi, konkurentlik (concurrency) va parallellik (parallelism), birinchisi asinxron vazifalar uchun, ikkinchisi esa izolyatsiyada ishlaydigan vazifalar uchun. Ushbu tushunchalarni tushunish uchun keling, ikkita stsenariyni tayyorlaymiz.

1-stsenariy (concurrency)

Siz qahvaxonada kofe ichmoqchisiz. (Kofe do'konini faqat eshikli yopiq xona deb hisoblang.) Uzun navbat bor, lekin siz o'z navbatingizni kutishga qaror qildingiz. Barista (kofe ichimliklar tayyorlovchi va xizmat ko'rsatuvchi shaxs) navbatdagi har bir keyingi mijozning buyurtmalarini birin-ketin qabul qiladi.

Aytaylik, barista A va barista B xizmarlari o'rtasida vaqtli farq bor. Mijozlar barista B ni buyurtmalar qabul qilishini kutishi va navbat bilan siljishlari kerak. Barista A va barista B xizmatlari o'rasidagi farq mijozlarning harakatlariga boshqa tarzda to'sqinlik qilmaydi, chunki ular hali ham bir-birlari bilan gaplashishi, o'zaro tanalari bilan aloqa qilishlari mumkin va xkz.

Dartdagi asinxron kod ham xuddi shunday ishlaydi: awaitkalit so'ziniasync funksiyasida ishlatganingizda, dasturning boshqa qismlari ishlashda davom etadi va async vazifa tugagach, kodning keyingi qatoridan davom etadi. Vazifa (Task) - bu kerak bo'lganda asyncdasturingizning turli qismlari (voqealarni qayta ishlash) o'rtasida o'tadigan Dart hodisalari tsikli. Quyidagi kod namunasi buni amalda ko'rsatadi.

void asyncTask() async {
   /// Ushbu vazifa o'tishi uchun 5 sekund kutiladi
   final result = await Future.delayed(Duration(seconds: 5));

   /// So'ng, kodning keyingi qismlari ishlashga o'tadi
   /// Kutish davomida, bu kodning boshqa qismlarini ishlashdan bloklamaydi
   print(result);
 }

2-stsenariy (parallelism)

Bu safar siz va do'stingiz qahvaxonaga borishga qaror qildingiz, lekin birinchi do'konda navbat juda ko'p va kechikishlar juda ko'p. Shunday qilib, do'stingiz sizni birinchi qahvaxonada qoldirib, keyingi qahvaxonaga borishga qaror qiladi.

Bu stsenariyda aytishimiz mumkinki, siz birinchi qahvaxonada kofe olishingiz va do'stingiz keyingi qahvaxonada qahva olish jarayoni parallel ravishda sodir bo'lmoqda, chunki birinchi qahvaxonada nima sodir bo'lishidan qat'i nazar, keyingi qahvaxonaga ta'sir qilmaydi.

Bir qator dasturlarni parallel ravishda ishga tushirish uchun izolyatsiyalar ishga tushadi. Ammo ular buni qanday qilishlarini muhokama qilishdan oldin, birinchi navbatda Dartdagi izolyatsiya nima ekanligini tushunishimiz kerak.

Izolyatsiya nima?

Gapirishdan oldin shuni ta'kidlash kerakki, Dart dasturlari "default" holat bo'yicha izolyatsiyalarda ishlaydi. Dartdagi izolyatsiyani kofe kioski ichidagi kofe ichimliklar tayyorlash va qahva buyurtma qilish kabi tadbirlar o'tkaziladigan kichkina xona deb o'ylab ko'ring. Izolyatsiya mashinadagi kichik bo'shliqqa o'xshaydi, uning xotirasi (qisqa muddatli asosda kerak bo'ladigan ma'lumotlarni saqlaydigan tizim) va kodni qayta ishlovchi voqealar sikli (event loop) da ishlaydigan bitta oqim.

Manba: https://www.youtube.com/watch?v=OLAXR0TCrcc

Dart "multithreading"

Esingizda bo'lsa, biz Dartni bir oqimli til deb aytgan edik. Ammo bizda ishlash uchun juda og'ir hisoblash kodi bo'lsa-chi? Bunday holda, vazifani asinxron usulda bajarish, ehtimol, ilovada kechikishga olib keladi, shuning uchun bizga bu vazifani boshqa ish oqimlarida bajarish usuli kerak.

Dartda multithreadingni amalga oshirish uchun biz yangi izolyatsiyani yaratishimiz kerak. Shuni ta'kidlash kerakki, Dartda izolyatsiyalar xotirani boshqa izolatlar bilan baham ko'rmaydi. Aksincha, ular bir-biridan butunlay ajratilgan (oh, bechora izolyatsiyalar ...) , va ularning nomi aynan shundan kelib chiqqan.

Yangi izolyatsiyada o'z xotirasi va voqea tsikllari bo'ladi. Buni mustaqil Dart dasturini ishga tushirish deb tasavvur qiling. Yaxshi yangilik bor: ikkita izolyatsiya xabarlarni oldinga va orqaga uzatish orqali bir-biri bilan muloqot qilishi mumkin. Quyida buning misoli keltirilgan.

https://medium.com/dartlang/dart-asynchronous-programming-isolates-and-event-loops-bffc3e296a6a

Dartda boshqa izolyatsiyani yaratishning ikki yo'li mavjud: Isolate.spawn()funktsiya va compute()funktsiya.

Isolate.spawn() yordamida yangi izolyatsiya yaratish

Keling, Flutter izolyatsiyasining ba'zi misollarini ko'rib chiqaylik. Izolyatsiyani yaratishning birinchi usuli Isolate.spawn()dan foydalanishdir. Biz birinchi parametr sifatida ishlatmoqchi bo'lgan usulga o'tamiz, ikkinchi argument esa biz izolyatsiyaga o'tmoqchi bo'lgan parametrdir.

Izolyatsiyaga uzatiladigan funksiya spawn()yuqori darajadagi funksiya *(sinf chegarasida bo‘lmagan funksiya) yoki statik usul bo‘lishi kerak.

Mana, izolyatsiyani yaratish uchun kod:

import 'dart:isolate';

void main() {
  Isolate.spawn<IsolateModel>(heavyTask, IsolateModel(355000, 500));
}

void heavyTask(IsolateModel model) {
 int total = 0;


 /// Belgilangan miqdordagi iteratsiyalarni bajaradi
 for (int i = 1; i < model.iteration; i++) {

   /// Har bir indexni ko'paytiruvchiga ko'paytiradi va jamini hisoblaydi
   total += (i * model.multiplier);
 }

 log("FINAL TOTAL: $total");
}

class IsolateModel {
 IsolateModel(this.iteration, this.multiplier);

 final int iteration;
 final int multiplier;
}

Ikki izolyatsiya o'rtasida muloqot qilish

Ikki izolyatsiya o'rtasidagi aloqani portlar ( ReceivePortva SendPort) orqali xabarlar yoki qiymatlarni yuborish orqali amalga oshirish mumkin. Ushbu portlar Stream kabi ishlaydi. Aslida Streammavhum sinfidan ReceivePort foydalanadi.

SendPort-ni ReceivePort-dan ReceivePort-ning sendPortqabul qilish usuli (getter) ni chaqirish orqali yaratish mumkin . Bu xabarlar ReceivePort-ga yuboriladigan vositadir. Boshqa tomondan, ReceivePort o'zining SendPort-dan xabarlarni tinglaydi.

Ushbu jarayonning vizual tasvirini ko'rish uchun quyidagi rasmga qarang:

Ikki izolyatsiya o'rtasida xabar qanday yuborilishi tasvirlangan rasmli illyustratsiya. SenderPort SendPort orqali xabar sifatida yuborilishi mumkin, bu esa qabul qiluvchiga qayta aloqa qilish uchun ruxsat beradi.

Mana, izolyatsiyalar orasidagi aloqani ko'rsatadigan kod parchasi. Diqqat - kod biroz chalkash. Bu qanday ishlashini tushunishingizga yordam berish uchun ba'zi izohlar qo'shdim. Kodni yozish va ishga tushirish ham yordam beradi. Kodni o'qiyotganda, Maykni do'stingizning ismi deb hisoblang.

import 'dart:isolate';

void main() {
 createIsolate();
}

Future createIsolate() async {
 /// Where I listen to the message from Mike's port
 ReceivePort myReceivePort = ReceivePort();

 /// Spawn an isolate, passing my receivePort sendPort
 Isolate.spawn<SendPort>(heavyComputationTask, myReceivePort.sendPort);

 /// Mike sends a senderPort for me to enable me to send him a message via his sendPort.
 /// I receive Mike's senderPort via my receivePort
 SendPort mikeSendPort = await myReceivePort.first;

 /// I set up another receivePort to receive Mike's response.
 ReceivePort mikeResponseReceivePort = ReceivePort();

 /// I send Mike a message using mikeSendPort. I send him a list,
 /// which includes my message, preferred type of coffee, and finally
 /// a sendPort from mikeResponseReceivePort that enables Mike to send a message back to me.
 mikeSendPort.send([
   "Mike, I'm taking an Espresso coffee",
   "Espresso",
   mikeResponseReceivePort.sendPort
 ]);

 /// I get Mike's response by listening to mikeResponseReceivePort
 final mikeResponse = await mikeResponseReceivePort.first;
 log("MIKE'S RESPONSE: ==== $mikeResponse");
}

void heavyComputationTask(SendPort mySendPort) async {
 /// Set up a receiver port for Mike
 ReceivePort mikeReceivePort = ReceivePort();

 /// Send Mike receivePort sendPort via mySendPort
 mySendPort.send(mikeReceivePort.sendPort);

 /// Listen to messages sent to Mike's receive port
 await for (var message in mikeReceivePort) {
   if (message is List) {
     final myMessage = message[0];
     final coffeeType = message[1];
     log(myMessage);

     /// Get Mike's response sendPort
     final SendPort mikeResponseSendPort = message[2];

     /// Send Mike's response via mikeResponseSendPort
     mikeResponseSendPort.send("You're taking $coffeeType, and I'm taking Latte");
   }
 }
}

Yuqoridagi kodni haddan tashqari ortiqcha ishlab chiqilgan deb aytish mumkin, buning sababi shundaki, izolyatsiyalar faqat asosiy oqimda kechikishga olib kelmaslik uchun boshqa oqimda bajarilishi kerak bo'lgan og'ir hisoblash ishlarini bajarishda ishlatilishi kerak. Ya'ni, yuqoridagi kodning maqsadi turli xil izolyatsiyalar bir-biri bilan qanday aloqa qilishini ko'rsatishdir.

Izolyatsiya o'zining voqealar siklidagi barcha hodisalarni amalga oshirganda, Dart avtomatik ravishda izolyatsiyani o'ldiradi. Izolyatsiyani qo'lda o'ldirish uchun isolate.kill()dan foydalaning. Izolyatsiyani to'xtatib turish uchun isolate.pause() dan foydalaning . To'xtatilgan izolyatsiyani davom ettirish uchun isolate.resume() dan foydalaning.

compute() yordamida yangi izolyatsiya yaratish

Yangi izolyatsiyani yaratishning eng oson yo'li - compute() dan foydalanish, garchi o'zaro kelishuv shundaki, siz ko'plab moslashuvchan Isolate.spawn() bergan imkoniyatlarni yo'qotasiz.

compute<Q, R>()ikkita umumiy turni qabul qiladi: Qbu izolyatsiya funksiyasi parametr turi va Rizolyatsiya funksiyasining qaytish turi. Quyidagi kod parchasi buni qanday amalga oshirishni ko'rsatadi:

import 'package:flutter/foundation.dart';

void main() {
  compute<IsolateModel, void>(heavyTask, IsolateModel(355000, 500));
}

void heavyTask(IsolateModel model) {
int total = 0;

/// Performs an iteration of the specified count
for (int i = 1; i < model.iteration; i++) {

  /// Multiplies each index by the multiplier and computes the total
  total += (i * model.multiplier);
}

log("FINAL TOTAL: $total");
}

class IsolateModel {
IsolateModel(this.iteration, this.multiplier);

final int iteration;
final int multiplier;
}

Xulosa

Xulosa qilib aytadigan bo'lsak, Dart-da "multithread" larni o'tkazish mumkin, garchi Dart bir-treadli til bo'lsa ham. Bitta ogohlantirish bor: Dart ilovalari allaqachon juda tez va yuqori darajada optimallashtirilgan va ko'pincha siz izolyatsiyadan foydalanishingiz shart emas. Uni faqat og'ir hisoblash ishlarini bajarish kerak bo'lganda foydalaning. Shuningdek, biz voqea tsikllari nima ekanligini va ular Dartda qanday ishlashini muhokama qildik va izolyatsiyalar xotirani baham ko'rmasa ham, qanday qilib muloqot qilishini ko'rib chiqdik. Sog' bo'ling!

Manba: https://blog.codemagic.io/understanding-flutter-isolates/

Telegram: @flutterblogs