Флаттер/Дарт: можете ли вы запустить фоновую службу с будущим.задерживается?

#flutter #dart #background-process

Вопрос:

Мне приходится каждый день выполнять кучу задач в проекте Dart/Flutter. Вот как я сейчас это делаю:

 class TaskScheduler {
  DateTime _lastUpdate;
  bool _isRunning = false;

  void launchDailyTasks() async {
    //make sure tasks are not already scheduled
    if (_isRunning) return;

    //check last updates
    if (_lastUpdate == null) {
      SharedPreferences prefs = await SharedPreferences.getInstance();
      final _stamp = prefs.getInt(prefsKey(AppConstants.LAST_SYNC));
      if (_stamp != null) {
        _lastUpdate = DateTime.fromMillisecondsSinceEpoch(_stamp);
      } else {
        _lastUpdate = DateTime.now();
      }
    }

    if (_lastUpdate.isBefore(DateTime.now().add(Duration(days: 1)))) {
      _runWorkersLoop();
    } else {
      final _delay =
          DateTime.now().difference(_lastUpdate.add(Duration(days: 1)));
      Timer(_delay, () => _runWorkersLoop());
    }
  }

  void _runWorkersLoop() async {
    _isRunning = true;
    _startDailyTasks();
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(prefsKey(AppConstants.LAST_SYNC),
        DateTime.now().millisecondsSinceEpoch);
    _lastUpdate = DateTime.now();
    Future.delayed(Duration(days: 1), () => _runWorkersLoop());
  }

}
 

И поэтому я задавался вопросом: это неправильно? Почему я должен использовать такой пакет, как https://pub.dev/packages/cron чтобы сделать это, если это сработает?

Комментарии:

1. Вы уверены, что это работает? Что произойдет, если, например, Android по какой-то причине решит убить ваш процесс? Тогда это перестанет работать.

2. @Сокрушительная правда, но мне все равно, так как мне не нужны данные из фона. Эти задачи должны выполняться только тогда, когда приложение работает, и всегда запускаются при запуске приложения.

Ответ №1:

При рассмотрении вашего примера вы сначала используете Timer (), а затем _runWorkersLoop() реализует свой собственный «периодический» цикл, вызывая себя с помощью Future.задержка(). Один из способов, возможно, упростить это, — использовать Timer.periodic (), который вы вызываете один раз, и пока вы его не отмените, он будет повторяться.

https://api.dart.dev/stable/2.12.0/dart-async/Timer/Timer.periodic.html

Затем у вас есть экземпляр таймера, который вы можете проверить, работает ли он с isRunning (), и вы можете отменить его в любое время с помощью cancel().

Я посмотрел на источник для cron lib, и он использует Future.microtask, который похож на Future.отложенный. Как правило, использование подобной библиотеки поможет вам:

  • больше внимания уделяется коду для исправления любых ошибок по сравнению с домашним решением
  • более общие функции, которые вы, возможно, захотите использовать позже
  • проще для изучения/понимания для тех, кто берет ваш код с помощью большего количества доступных примеров использования

Я предполагаю, что у вас нет никаких критических требований к времени вплоть до миллисекунды, когда ваши вещи запускаются, поэтому я думаю, что вам может быть интересно взглянуть на периодический таймер, как упоминалось выше.

Одна вещь, которую вы, возможно, захотите защитить, заключается в том, что если ошибка позже вызовет вашу функцию _runWorkersLoop (), когда она уже запущена, она вызовет Future.снова отложено (), хотя один уже ждет. У вас нет возможности проверить существующее будущее.экземпляры с задержкой (), но с Timer.peridic() вы можете использовать «isRunning()» для проверки.

Комментарии:

1. Спасибо, что указали на это, я определенно изменю это. В противном случае нет риска сбоя приложения в фоновом режиме или утечки памяти? Я думал, что мой подход был слишком простым, чтобы работать должным образом…

2. Я не верю, что существует какой-либо риск утечки памяти или сбоев, в любом случае, из-за этого. 🙂 Если вы еще не смотрели видео с флаттером в асинхронном режиме, я бы очень рекомендовал их. youtube.com/watch?v=OTS-ap9_aXc

Ответ №2:

Вот улучшенная версия после комментария @Eradicatore, на случай, если кто-то заинтересуется. Работает, как и было обещано.

 class TaskScheduler {
  Timer _timer;

  /// Launch the daily tasks.
  /// If [forceUpdate] is true, any currently running task will be canceled and rerun.
  /// Otherwise a new task will only be started if no previous job is running.
  void launchDailyTasks({bool forceUpdate = false}) async {
    bool checkLastSync = true;
    if (forceUpdate) {
      //cancel
      if (_timer != null) _timer.cancel();
      checkLastSync = false;
    } else {
      //don't start tasks if a previous job is running
      if (_timer != null amp;amp; _timer.isActive) {
        return;
      }
    }

    Duration startDelay = Duration();
    if (checkLastSync) {
      //check last sync date to determine when to start the timer
      SharedPreferences prefs = await SharedPreferences.getInstance();
      int timestamp = prefs.getInt(prefsKey(AppConstants.LAST_SYNC));
      if (timestamp == null) timestamp = DateTime.now().millisecondsSinceEpoch;
      final difference = DateTime.now()
          .difference(DateTime.fromMillisecondsSinceEpoch(timestamp));
      if (difference.inDays < 1) {
        //start the timer when 1 day is reached
        startDelay = Duration(
            seconds: Duration(days: 1).inSeconds - difference.inSeconds);
      }
    }


    //start tasks
    Future.delayed(startDelay, () {
      //run first tasks immediately once
      _runWorkersLoop();
      //setup periodic after
      _timer = Timer.periodic(Duration(days: 1), (Timer t) {
        _runWorkersLoop();
      });
    });
  }

  void _runWorkersLoop() async {
    _startDailyTasks();
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(prefsKey(AppConstants.LAST_SYNC),
        DateTime.now().millisecondsSinceEpoch);
  }

}