Irbisa · Шпаргалка
Изоляты и настоящая многопоточность
Isolate.run, spawn, порты, копирование сообщений, пулы воркеров, TransferableTypedData
Как
ReceivePortиSendPortпозволяют двум изолятам общаться и почему это безопаснее потоков с общей памятью?Flutter однопоточный? Какие потоки есть у Flutter-приложения и какой из них выполняет ваш Dart-код?
В чём разница между
compute(),Isolate.run()иIsolate.spawn()и когда нужен именноspawn?// one argument in, one value outFuture<List<Order>> parse(String body) => compute(parseOrders, body, debugLabel: 'parseOrders'); // a closure: captures several inputs, and may be asyncFuture<Report> buildReport(List<OrderRow> rows, Filter filter) =>…Найдите баг: индикатор загрузки замирает на полсекунды, когда приходит отчёт, хотя
loadобъявленasync.class ReportRepository { ReportRepository(this._api); final ReportApi _api; Future<Report> load() async { final body = await _api.fetchReport(); // ~8 MB of JSON return _parse(body); } Future<Report> _parse(String body) async { final json = await Future(() => jsonDecode(body) as Map<String, dynamic>); return Report.fromJson(json); }}Что выведет этот код и почему воркер не видит значение, которое выставил главный изолят?
import 'dart:isolate'; int counter = 0; Future<void> main() async { counter = 7; final fromWorker = await Isolate.run(() { counter += 100; return counter; }); print(fromWorker); print(counter);}Вызов
Isolate.runвнутриStateпадает с «object is unsendable»: что он на самом деле пытается отправить?class _ReportPageState extends State<ReportPage> { final _scroll = ScrollController(); String _raw = ''; List<OrderRow> _rows = const []; Future<void> _parse() async { final rows = await Isolate.run(() => parseRows(_raw)); setState(() => _rows = rows); } // build() omitted}Этот handshake с воркером падает с «Bad state: Stream has already been listened to». Что не так и как должен выглядеть handshake?
Future<SendPort> startWorker() async { final fromWorker = ReceivePort(); await Isolate.spawn(workerMain, fromWorker.sendPort); final toWorker = await fromWorker.first as SendPort; fromWorker.listen((reply) => handleReply(reply)); return toWorker;} void workerMain(SendPort toMain) { final inbox = ReceivePort(); toMain.send(inbox.sendPort); inbox.listen((job) => toMain.send(process(job)));}Исключение внутри запущенного изолята не доходит до вашего
try/catch, а экран крутит лоадер вечно: куда делась ошибка?Future<Isolate> startImporter(SendPort results, void Function() failAll) { final errors = ReceivePort(); final exits = ReceivePort(); errors.listen((pair) { final [error, stack] = pair as List; // two Strings, never the object reporter.record(error as String, StackTrace.fromString(stack as String? ?? ''));…Вы вызываете
isolate.kill()для воркера, застрявшего в двухсекундном цикле, а он всё равно доводит цикл до конца: что на самом деле обещаетkill?void spinner(SendPort toMain) { final sw = Stopwatch()..start(); while (sw.elapsedMilliseconds < 2000) {} // no await: one single event toMain.send('finished');}…Найдите баг: каждый раз, когда пользователь открывает и закрывает экран сканирования, расход памяти и CPU растёт и больше не падает.
class _LiveScanState extends State<LiveScan> { final _results = ReceivePort(); final _search = TextEditingController(); int _found = 0; @override void initState() { super.initState(); _results.listen((msg) => setState(() => _found = msg as int)); Isolate.spawn(scanFolder, _results.sendPort, debugName: 'scanner'); } @override void dispose() { _search.dispose(); super.dispose(); }}Что
SendPort.sendделает с вашим объектом и почему 40 МБ вUint8Listотправляются намного дольше, чем 40 МБ вString?Что такое группа изолятов и как
Isolate.exitвозвращает большой результат без копирования?// one-shot worker: the result is handed over, not copiedFuture<Report> buildReport(List<OrderRow> rows) async { final port = ReceivePort(); await Isolate.spawn(_build, (port.sendPort, rows)); return await port.first as Report;}…Что на самом деле экономит
TransferableTypedDataи когда обычныйsendсUint8Listничем не хуже?// worker: join the encoded chunks once, then move the buffervoid encoderMain(SendPort toMain) { final chunks = <Uint8List>[for (final frame in frames()) encode(frame)]; final moved = TransferableTypedData.fromList(chunks); // the one copy toMain.send(moved); // constant time, whatever the size // moved.materialize(); // would throw ArgumentError: it left with the send…Вы вызываете
Isolate.runна каждую строку списка, а сетка всё равно дёргается: когда долгоживущий воркер лучше, чем запуск изолята на каждую задачу?class Thumbnailer { // handshake as in the spawn example: the worker sends its SendPort first Thumbnailer(this._requests); final SendPort _requests; // reply listener: _pending.remove(id)!.complete(bytes), or completeError(error) final _pending = <int, Completer<Uint8List>>{};…Экспорт идёт тридцать секунд, и ему нужен прогресс-бар и рабочая кнопка «Отмена»: как получить и то, и другое от запущенного изолята?
void exportMain(SendPort toMain) { final control = ReceivePort(); // a ReceivePort cannot be sent, toMain.send(control.sendPort); // so send its SendPort back first var cancelled = false; control.listen((msg) { if (msg == 'cancel') cancelled = true;…Одно изображение на 40 мегапикселей занимает пул генерации миниатюр, и вся сетка встаёт: как выбрать размер пула и распределять задачи?
compute()во Flutter web возвращает правильный ответ, но вешает вкладку на две секунды: что он там делает?// work.dart: one import for every platformexport 'work_io.dart' if (dart.library.js_interop) 'work_web.dart'; // work_io.dart (mobile and desktop): a real isolateFuture<int> countWords(String text) => Isolate.run(() => countWordsSync(text));…Память растёт, пока работает воркер экспорта, а CPU-профайлер показывает простаивающее приложение: как профилировать запущенный изолят?