← Назад к вопросам

Как создать macrotask?

2.0 Middle🔥 181 комментариев
#Асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Создание 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 для:

  1. Отложить выполнение после текущего фрейма

    Future(() {
      // Выполнится в следующем macrotask
    });
    
  2. Batching операций для оптимизации

    Future.delayed(Duration(milliseconds: 50)).then((_) {
      processBatch();
    });
    
  3. Avoid jank при обновлении UI

    setState(() { ... });
    Future(() {
      // Аналитика/побочные эффекты
    });
    
  4. Debounce/Throttle пользовательского ввода

    Timer(Duration(milliseconds: 300), () {
      // Поиск после остановки ввода
    });
    

❌ НЕ ИСПОЛЬЗУЙ для:

  1. Если нужна максимальная скорость (используй microtask)
  2. Если нужна гарантированно срочная работа
  3. Если можно решить 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.