#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);
}
}