Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Создание Macrotask в Dart/Flutter
Macrotask — это асинхронная задача, которая выполняется в macrotask queue (event loop очередь). Это отличается от microtask, которая выполняется до того, как обработается следующий macrotask. Для Flutter разработчика это критично при работе с асинхронным кодом и оптимизацией производительности.
Понимание Event Loop в Dart
Dart использует event loop (цикл обработки событий) с двумя очередями:
┌─────────────────────────────────────┐
│ Dart Event Loop │
├─────────────────────────────────────┤
│ │
│ 1. Выполни Dart код │
│ │
│ 2. Обработай MICROTASK queue: │
│ - Futures with .then() │
│ - scheduleMicrotask() │
│ │
│ 3. Выполни MACROTASK queue: │
│ - Timer │
│ - I/O операции │
│ - Event от пользователя │
│ │
│ 4. Перерисуй UI (Flutter) │
│ │
│ 5. Повтори │
│ │
└─────────────────────────────────────┘
Создание Macrotask: Future с задержкой
Способ 1: Future.delayed() — самый простой
void createMacrotask() {
// Создаёт macrotask, который выполнится после указанной задержки
Future.delayed(const Duration(seconds: 1)).then((_) {
print("Это выполнится как macrotask");
});
}
// Практический пример
void fetchUserData() {
print("Начало загрузки");
Future.delayed(const Duration(seconds: 2)).then((_) {
print("Данные загружены"); // Выполнится в macrotask queue
// Обновить UI
});
print("Загрузка запущена");
}
// Вывод:
// "Начало загрузки"
// "Загрузка запущена"
// (2 секунды ожидания...)
// "Данные загружены"
Способ 2: Timer — явное создание macrotask
import 'dart:async';
void createTimerMacrotask() {
// Timer создаёт macrotask в очереди
Timer(const Duration(milliseconds: 100), () {
print("Macrotask из Timer");
});
}
// Практический пример: Debounce
class SearchService {
Timer? _debounceTimer;
void onSearchChanged(String query) {
// Отменяем предыдущий timer
_debounceTimer?.cancel();
// Создаём новый macrotask (debounce)
_debounceTimer = Timer(const Duration(milliseconds: 500), () {
print("Поиск: $query");
performSearch(query);
});
}
void dispose() {
_debounceTimer?.cancel();
}
}
Способ 3: Future().then() — немедленный macrotask
void createImmediateMacrotask() {
// Future().then() создаёт macrotask, но без задержки
// Выполнится после всех microtask'ов
Future(() {
print("Это macrotask без задержки");
}).then((_) {
print("После первого macrotask'а");
});
}
// Вывод:
// (microtask'и)
// "Это macrotask без задержки"
// "После первого macrotask'а"
Разница: Microtask vs Macrotask
Важно понимать разницу для оптимизации:
void demonstrateDifference() {
print("1. Sync код");
// MICROTASK — выполнится раньше, чем macrotask
scheduleMicrotask(() {
print("3. Microtask");
});
// MACROTASK — выполнится после всех microtask'ов
Future.delayed(Duration.zero).then((_) {
print("5. Macrotask");
});
// Ещё код
Future(() {
print("4. Macrotask (без задержки)");
});
// Microtask тоже может быть через Future
Future.value().then((_) {
print("2. Это тоже microtask (Future.value)");
});
print("0. Sync код продолжение");
}
// Вывод:
// "1. Sync код"
// "0. Sync код продолжение"
// "2. Это тоже microtask (Future.value)"
// "3. Microtask"
// "4. Macrotask (без задержки)"
// "5. Macrotask"
Практические примеры
Пример 1: Отложить UI обновление
class CounterWidget extends StatefulWidget {
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int counter = 0;
void incrementCounter() {
setState(() {
counter++;
});
// Если нужно выполнить что-то ПОСЛЕ перерисовки
// Используй macrotask
Future(() {
print("UI обновлена, counter = $counter");
// Например, отправить аналитику
analytics.logEvent('counter_incremented', {'value': counter});
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: incrementCounter,
child: Text('Counter: $counter'),
);
}
}
Пример 2: Batch операции
class DatabaseService {
List<String> _batchQueue = [];
void queueOperation(String operation) {
_batchQueue.add(operation);
// Batch все операции за 100ms в один macrotask
Future.delayed(const Duration(milliseconds: 100)).then((_) {
if (_batchQueue.isEmpty) return;
print("Выполняю ${_batchQueue.length} операций")
processBatch(_batchQueue);
_batchQueue.clear();
});
}
void processBatch(List<String> operations) {
// Отправить все операции на сервер за один запрос
api.saveOperations(operations);
}
}
Пример 3: Animation с macrotask
class AnimatedWidget extends StatefulWidget {
@override
State<AnimatedWidget> createState() => _AnimatedWidgetState();
}
class _AnimatedWidgetState extends State<AnimatedWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
// Запустить анимацию как macrotask
// Это гарантирует, что UI готов
Future(() {
_controller.forward();
});
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: _controller,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Пример 4: Throttle обработчик событий
class ThrottledButton extends StatefulWidget {
final VoidCallback onPressed;
final Duration throttleDuration;
const ThrottledButton({
required this.onPressed,
this.throttleDuration = const Duration(milliseconds: 300),
});
@override
State<ThrottledButton> createState() => _ThrottledButtonState();
}
class _ThrottledButtonState extends State<ThrottledButton> {
bool _isThrottled = false;
void _handlePress() {
if (_isThrottled) return; // Игнорируй если throttled
_isThrottled = true;
widget.onPressed();
// Разблокируй в следующем macrotask
Future.delayed(widget.throttleDuration).then((_) {
if (mounted) {
setState(() {
_isThrottled = false;
});
}
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _handlePress,
child: const Text('Click me'),
);
}
}
Когда использовать Macrotask
✅ ИСПОЛЬЗУЙ macrotask для:
-
Отложить выполнение после текущего фрейма
Future(() { // Выполнится в следующем macrotask }); -
Batching операций для оптимизации
Future.delayed(Duration(milliseconds: 50)).then((_) { processBatch(); }); -
Avoid jank при обновлении UI
setState(() { ... }); Future(() { // Аналитика/побочные эффекты }); -
Debounce/Throttle пользовательского ввода
Timer(Duration(milliseconds: 300), () { // Поиск после остановки ввода });
❌ НЕ ИСПОЛЬЗУЙ для:
- Если нужна максимальная скорость (используй microtask)
- Если нужна гарантированно срочная работа
- Если можно решить synchronously
Performance Tips
// ❌ Плохо — создаёш макротаск для каждого символа
TextField(
onChanged: (value) {
Future.delayed(Duration(milliseconds: 100)).then((_) {
search(value);
});
},
)
// ✅ Хорошо — один debounce timer
TextField(
onChanged: (value) {
_searchDebounce?.cancel();
_searchDebounce = Timer(const Duration(milliseconds: 300), () {
search(value);
});
},
)
Итог
Macrotask в Dart/Flutter — это:
- Асинхронные задачи в очереди event loop'а
- Выполняются после всех microtask'ов и UI перерисовки
- Essential для отложения работы, debounce/throttle'а и batch операций
- Правильное использование улучшает производительность и responsiveness приложения
Павлик, помни: macrotask запускается медленнее чем sync код, но быстрее чем следующий фрейм UI.