Flutter/Dart:你能 运行 后台服务 Future.delayed 吗?

Flutter/Dart: can you run a background service with Future.delayed?

我每天必须在 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 这样的包来执行此操作?

回顾您的示例,您最初使用 Timer(),然后 _runWorkersLoop() 通过使用 Future.delayed() 调用自身来实现它自己的“周期性”循环。可能简化此操作的一种方法是使用 Timer.periodic() 您调用一次,直到您取消它,它会重复。

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

然后你有一个计时器实例,你可以检查它是否 运行 isRunning() 并且你可以随时使用 cancel() 取消。

我查看了 cron lib 的源代码,它使用类似于 Future.delayed 的 Future.microtask。通常使用这样的库可以帮助您:

  • 与自行开发的解决方案相比,更多关注修复任何错误的代码
  • 您以后可能想要使用的更多通用功能
  • 对于通过更多可用示例获取您的代码的人来说learn/understand更容易

我假设您在运行时没有任何精确到毫秒的关键计时要求,所以我认为您可能有兴趣查看上面提到的周期性计时器。

你可能想要保护的一件事是,如果错误稍后调用你的 _runWorkersLoop() 函数,而它已经 运行,它会再次调用 Future.delayed(),即使一个已经在等待.您没有办法检查现有的 Future.delayed() 实例,但是使用 Timer.peridic() 您可以使用“isRunning()”进行检查。

这是@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 && _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);
  }

}