Ответ
Асинхронность в Dart построена вокруг Event Loop, Futures и Streams. Это однопоточная модель, которая не блокирует выполнение на операциях ввода-вывода (I/O).
1. Основные абстракции
Future<T>: Представляет результат отложенной (асинхронной) операции, которая завершится одним значением (или ошибкой) в будущем.Stream<T>: Представляет последовательность асинхронных событий (данных или ошибок) во времени.
2. Event Loop и Очереди
Dart runtime управляет двумя очередями:
- Microtask Queue: Для высокоприоритетных задач (например, завершение
Futureчерезcompleter.complete()). - Event Queue: Для внешних событий (таймеры, I/O, жесты, отрисовка кадров).
Event Loop постоянно работает: сначала выполняет все задачи из Microtask Queue, затем одну задачу из Event Queue, и так по кругу. Это предотвращает «голодание» событий.
3. Практическое использование
С Future и async/await:
Future<String> fetchUserData() async {
// Имитация сетевого запроса.
await Future.delayed(Duration(seconds: 1));
// Имитация декодирования JSON.
return '{"name": "Alice"}';
}
void main() async {
print('Fetching data...');
try {
final jsonString = await fetchUserData(); // Не блокирует поток.
final user = jsonDecode(jsonString);
print('User: ${user['name']}');
} catch (e) {
print('Error: $e');
}
print('This runs after data is fetched.');
}
Работа со Stream:
Stream<int> timedCounter(Duration interval, int maxCount) async* {
int count = 0;
while (count < maxCount) {
await Future.delayed(interval);
yield ++count; // «Выдаем» значение в поток.
}
}
void main() async {
final stream = timedCounter(Duration(seconds: 1), 5);
await for (final count in stream) {
print('Count: $count'); // Печатает 1, 2, 3, 4, 5 с интервалом в секунду.
}
}
4. Параллелизм: Isolates
Для выполнения CPU-интенсивных задач, чтобы не блокировать Event Loop, используются Isolates. Это отдельные потоки выполнения с собственной памятью, общающиеся через передачу сообщений.
void heavyComputation(SendPort sendPort) {
// Долгий расчет в отдельном изоляте.
final result = _calculatePi(100000);
sendPort.send(result);
}
void main() async {
final receivePort = ReceivePort();
await Isolate.spawn(heavyComputation, receivePort.sendPort);
receivePort.listen((message) {
print('Result from isolate: $message');
receivePort.close();
});
}
Итог: Dart использует неблокирующую асинхронную модель на основе событий, что идеально подходит для I/O-задач в Flutter (сеть, файлы, БД), сохраняя интерфейс плавным.
Ответ 18+ 🔞
А, ну это про асинхронность в Dart! Давай разберём, как эта штука работает, а то многие думают, что там магия какая-то, а на самом деле всё довольно логично, хоть и местами мозг выносит.
Смотри, главная фишка в том, что у тебя один основной поток — Event Loop, который как заведённый хомяк в колесе бегает по кругу. И он нихуя не блокируется на долгих операциях, типа сетевых запросов или чтения файлов. Вместо этого он просто говорит: "Окей, я пока это отложу, а сам пойду другие дела делать".
Основные кирпичики этой конструкции:
Future<T>— это типа обещание. "Братан, я тебе значение принесу, но попозже, ок?" Может быть успешным или с ошибкой, но только один раз.Stream<T>— это уже не обещание, а целая лента событий. Как водопроводный кран: открыл — полилось, закрыл — перестало. Значений может быть овердохуища.
А теперь самое вкусное — как это всё крутится.
У нашего хомяка (Event Loop) есть две кормушки, куда ему подкидывают задачи:
- Microtask Queue — типа VIP-очередь. Сюда попадают срочные внутренние дела, типа когда ты вручную завершаешь Future. Хомяк сначала ВСЁ выгребает отсюда дочиста.
- Event Queue — обычная очередь для всех остальных: таймеры, отклики от сети, клики пользователя. Отсюда хомяк берёт по одной задаче за раз, делает её, и снова смотрит в VIP-очередь.
Вот эта хитрая жопа с приоритетами не даёт интерфейсу виснуть. Пока одна долгая задача из Event Queue ждёт ответа от сервера, хомяк успевает перерисовать экран десять раз и обработать твой тап.
На практике это выглядит как-то так:
С Future и async/await — это просто сахар, чтобы код выглядел почти как синхронный, но без блокировок.
Future<String> fetchUserData() async {
// Представь, что тут запрос к серверу. Мы его "await'им" — то есть говорим:
// "Хомяк, иди пока другие дела делай, а как ответ придет — вернись сюда".
await Future.delayed(Duration(seconds: 1));
return '{"name": "Alice"}';
}
void main() async {
print('Начинаю грузить данные...');
try {
// Тут программа НЕ ВИСИТ. Хомяк пошёл крутить другие задачи.
final jsonString = await fetchUserData();
final user = jsonDecode(jsonString);
print('Пользователь: ${user['name']}');
} catch (e) {
print('Ёпта, ошибка: $e'); // На случай, если сервер лёг.
}
print('А это выполнится уже ПОСЛЕ получения данных.');
}
Со Stream — тут уже веселее, можно подписываться на потоки данных.
Stream<int> timedCounter(Duration interval, int maxCount) async* {
int count = 0;
while (count < maxCount) {
await Future.delayed(interval); // Ждём интервал.
yield ++count; // А вот тут "выплёвываем" значение в поток.
}
}
void main() async {
final stream = timedCounter(Duration(seconds: 1), 5);
// "await for" — это типа "жду каждое новое значение из потока".
await for (final count in stream) {
print('Счётчик: $count'); // Напечатает 1, 2, 3, 4, 5 с интервалом в секунду.
}
}
А если задача такая, что мозг выносит? Например, посчитать число Пи до миллионного знака. Вот тут Event Loop, конечно, охренеет и всё зависнет. Для такого есть Isolates.
Изоляты — это как отдельные квартиры с собственными хомяками и памятью. Они не шастают по твоей памяти, а общаются через почтовые ящики (SendPort/ReceivePort). CPU-интенсивную хуйню нужно пихать туда.
void heavyComputation(SendPort sendPort) {
// Этот код выполняется в СОВСЕМ ДРУГОМ изоляте, параллельно.
final result = _calculatePi(100000); // Долгий расчёт.
sendPort.send(result); // Отправляем результат обратно в основную квартиру.
}
void main() async {
final receivePort = ReceivePort(); // Наша почтовая ящик.
// Спавним новый изолят и даём ему адрес ящика.
await Isolate.spawn(heavyComputation, receivePort.sendPort);
// Подписываемся на ящик и ждём письмо.
receivePort.listen((message) {
print('Результат от изолята: $message');
receivePort.close(); // Ящик больше не нужен.
});
}
Итог, ёпта: Dart не блокирует главный поток на I/O операциях, поэтому твой Flutter-интерфейс остаётся плавным. Всё строится на Future (разовое обещание) и Stream (поток событий). А если нужно что-то реально тяжёлое — гоним в отдельный Isolate, чтобы не заставлять нашего хомяка таскать кирпичи.