Ну вот она самая интересная тема по моему мнению)
Про процессы, потоки
Во время танца мы управляем не только руками, ногами и головой, но и делаем множество интересных и точных движений, которые могут быть выполнены только продвинутыми танцорами с высоким уровнем развития мозга. Каждое движение можно рассматривать как процесс, а компьютеры, как известно, бывают четырехъядерные, восьмиядерные и т.д.
Обычно Количество возможных процессов зависит от количества ядер в компьютере, и аналогично, от уровня мозговой активности танцора зависит насколько будет красиво исполнение сложных движений. То есть восьми ядерный процессор способен выполнить параллельно только 8 процессов.
Таким образом, танцоры-профессионалы можно сравнить с многоядерными компьютерами, в то время как мы, обычные люди, являемся одноядерными, делая простые движения руками или ногами.
Процесс — окружение или среда выполнения для запуска программы(набора файлов) на ОС. Создав, предоставляет доступ к системным ресурсам: память, устройства ввода\вывода, камера и тд.
Потоки — это единицы выполнения (unit of execution), которые выполняются на процессоре. В одном процессе может находится несколько потоков. Такие потоки будут иметь общую память.
Когда мы открываем flutter-приложение(или dart-код) оно запускается в одном процессе и в главном потоке.

На картинке мы видим что каждому процессу выделяется память, к которой имеют доступ все потоки этого процесса.
Асинхронность и параллельность
И так, ты знаешь async, await, Future и думаешь что победил время.
Попробуй асинхронно запустить код, которое занимает достаточное время и ты увидишь, как твоя анимация затормозит, а само приложение перестанет отвечать на действия пользователя. Основной изолят будет перегружен.
Вот например из официальной документации:
const String filename = 'with_keys.json'; void main() async { // Read some data. final fileData = await _readFileAsync(); // ожидание результата сложной функции // приложение остановливается final jsonData = jsonDecode(fileData); // Use that data. print('Number of JSON keys: ${jsonData.length}'); } Future<String> _readFileAsync() async { final file = File(filename); final contents = await file.readAsString(); return contents.trim(); }
Или даже какая-то такая функция:
int value = 0; for (var i = 0; i < 5000000; i++) { value += i; print(value); }
Попробуйте ее запустить в DartPad.
Асинхронность это хорошо, но для таких задач как поход в сеть или получение данных, где основную работу выполняет сервер.
А вот параллельность поможет нам в приложения производить затратные операции без потери производительности и времени.
Асинхронность работает в одном потоке, параллельность в двух и более.
Мы привыкли путать параллельность, многопоточность, асинхронность.
Ты можешь почитать эти статьи , если тебе интересна эта тема :
Параллелизм против многопоточности против асинхронного программирования: разъяснение
Concurrency and Parallelism in Dart and how it is used in Flutter
Превращаем приложение в super-Танцора. Isolates

И так если загрузить майн EventLoop (главный изолят) сложной задачей вы получите блокировку потока, на то время пока не выполнится эта задача. Для реализации параллельного выполнения кода в Dart есть Isolate:
У каждого isolate есть свой поток, память и следовательно и EventLoop.
Гарантируется что в одном потоке выполняется одновременно только один изолят.
Поток не привязан к конкретному изоляту.
Общение между изолятами достигается с помощью сообщений, которые передаются через порты
SendPortиReceivePort.
Процесс содержит в себе потоки, которые имеют общую память. А Изоляты не имеют общей памяти именно поэтому Isolate != поток.
Преимущества
так как изолят имеет свою память и общается по портам, похожими механизмами могут похвастаться совсем немногие языки. Такие языки как JAva,Kotlin, C++ используют спокойно используют thread и корутины, однако подход в dart имеет свои плюсы:
Параллелизм с общим состоянием подвержен ошибкам и может привести к усложнению кода.
Отсутствие общего состояния между изолятами означает, что сложности параллелизма, такие как мьютексы или блокировки и перегонки данных, не будут возникать в Dart.
Недостатки
Веб-платформа Dart, однако, не поддерживает изоляты. Веб-приложения Dart могут использовать web workers для запуска сценариев в фоновых потоках, аналогичных изолятам.
Невозможность работать с UI. Сторонний поток не может получить доступ к памяти и к всему что происходит внутри главного main thread, в котором как раз таки и находится UI.
В изолят может быть передана только глобальная функция или статический методы, как просто набор простых операций для выполнения. Так как методы или объекты существуют в своем контексте и мы не можем его предать в другой изолят.
Время на передачу сообщений между изолятами. Если в многопоточности мы просто передавали ссылки на объекты и это не занимало много времени, то в dart идет копирование данных, которое занимает достаточно много времени.
Простой способ создания Isolate
Будем использовать compute() или Isolate.run()

Isolate.run() упрощаются шаги по настройке рабочих изолятов и управлению ими: Порождает (запускает и создает) изолят
Запускает функцию для созданного изолята
Фиксирует результат
Возвращает результат в основной изолят
Завершает изолят после завершения работы
Проверяет, фиксирует и отправляет исключения и ошибки обратно в основной изолят
Передача одного сообщения от Изолята в Главный

Создаем порт для главного изолята, как бы трубу которая будет предназначена для получения сообщений.

Передаем функцию и параметры нужные для нее, в данном примере SendPort.
SendPortпо сути, просто указывает на свойReceivePort, а также сохраняет идентификатор того,Isolateгде он был создан. Любое сообщение, отправленное через этотSendPort, доставляется на этотReceivePort, из которого оно было создан.У
ReceivePort(порта приема) может быть многоSendPort(портов отправки).


import 'dart:isolate'; void main() async{ final receivePort = ReceivePort(); // создаем порт для главного изолята, как бы трубу которая будет предназанчена для получения сообщений. final isolate = await Isolate.spawn(count, receivePort.sendPort); receivePort.listen((message) { print(message); receivePort.close(); isolate.kill(); }); print("Some work..."); } void count(SendPort sendPort) { var result = 0; for (var i = 1; i <= 50000000000; i++) { result = i; } sendPort.send(result); }
Двухстороннее общение
Пример из статьи, мне он показался интересным, я его немного изменила.
import 'dart:async'; import 'dart:isolate'; void main() => Future<void>(() async { final mainIsolatePort = ReceivePort(); // создаем порт получения для главного изолята final isolate = await Isolate.spawn<SendPort>( entryPoint, mainIsolatePort.sendPort, errorsAreFatal: true, debugName: 'MyIsolate', ); // создаем новый изолят, указываем входную функцию, // передаем порт отправки для главного изолята. // errorsAreFatal: true гарантирует, что неперехваченные ошибку приведут // к уничтожению нового изолята. // debugName: 'MyIsolate' дает имя изоляту. final completer = Completer<SendPort>(); // для чего комплитор расскажу ниже //(ожидаем когда из нового изолята придет сообщние с его портом отправки) mainIsolatePort.listen((message) { if (message is SendPort) completer.complete(message); if (message is String) print(message); }); final send2Isolate = await completer.future; // Получаем SendPort. send2Isolate.send(7); send2Isolate.send('kjb'); send2Isolate.send(9); // Отправляем сообщения новому изоляту. await Future<void>.delayed(const Duration(seconds: 1)); mainIsolatePort.close(); // Close the ReceivePort. isolate.kill(); // Kill the isolate. }); void entryPoint(SendPort mainIsolatePortSend) { final isolatePort = ReceivePort(); // Создаем ReceivePort у нового изолята, // для того что бы он мог получать сообщение от главного. mainIsolatePortSend.send(isolatePort.sendPort); // Send the SendPort to the main isolate. isolatePort.listen((message) { if (message is! int) return; // Ignore messages of other types. for (var i = 1, r = 1; i <= message; i++, r *= i) { // отправляем результат главному изоляту через порт получения главного изолята, // который мы передади в эту входную функцию. mainIsolatePortSend.send('$i! = $r'); } } // слушаем порт получения, если сообщение не число, мы игнорируем его. ); }
Мы создаем для каждого изолята ReceivePort, SendPort.Я бы представила это как-то так:

Помним о том, чтоReceivePortмогут иметь несколько SendPort, поэтому картинка могла бы выглядеть так, если бы новый изолят общался бы с другими изолятами:

Комплитор здесь создан для того что бы дождаться сообщения от нового изолята sendPort и затем использовать его.

Как ты видишь изолят ни как не обработал строку, отправленную главным изолятом.
send2Isolate.send(7); send2Isolate.send('kjb'); send2Isolate.send(9);
На это знакомство с изолятами закончено :)
Рекомендую почитать:
О Isolate.spawnUri узнаешь тут: https://dart.dev/language/concurrency
О улучшении кода при работе с изолятами тут: https://plugfox.dev/mastering-isolates/
Как тебе статья?)
