How to periodically call a function only if it has finished executing the previous time in Dart?

1,572

Solution 1

This code should work correctly:

import 'dart:async';
import 'dart:math';

Future<void> main() async {
  Timer(Duration(seconds: 0), _callback);
}

Future<void> _callback() async {
  print('started: ${DateTime.now()}');
  try {
    final ms = Random().nextInt(5000);
    await Future.delayed(Duration(milliseconds: ms));
  } finally {
    print('finished: ${DateTime.now()}');
    Timer(Duration(seconds: 5), _callback);
  }
}

Result:

started: 2020-01-04 17:51:29.401246
finished: 2020-01-04 17:51:32.972114
started: 2020-01-04 17:51:37.973218
finished: 2020-01-04 17:51:38.551234
started: 2020-01-04 17:51:43.552218
finished: 2020-01-04 17:51:47.579374
started: 2020-01-04 17:51:52.580253
finished: 2020-01-04 17:51:55.547275
started: 2020-01-04 17:52:00.548258
finished: 2020-01-04 17:52:01.156281

Solution 2

You can use package https://pub.dev/packages/neat_periodic_task
It has the following features

  • Run a periodic task on a loop.
  • Limit maximum run-time to prevent a hanging loop.
  • Retry failed task executions after a timeout.
  • Catch and log all exceptions/errors from the task.
  • (Optionally) prevent concurrent tasks using status in a storage service that supports optimistic concurrency (eg. Cloud Datastore, Google Cloud Storage).
  • Print log messages liveness monitoring of the periodic tasks.

example code snippet

import 'dart:io';
import 'package:neat_periodic_task/neat_periodic_task.dart';

void main() async {

  final scheduler = NeatPeriodicTaskScheduler(
    interval: Duration(seconds: 5),
    name: 'hello-world',
    timeout: Duration(seconds: 5),
    task: () async => print('Hello World'),
    minCycle: Duration(seconds: 5),
  );

  scheduler.start();
  await ProcessSignal.sigterm.watch().first;
  await scheduler.stop();
}

Solution 3

I don't know exactly what return type the DB call has (if any, I'm assuming List<int> here), but for 5 seconds or (execution time + 5 seconds) you could use something like this simulation:

import 'dart:math';

// simulating network call
Future<List<int>> _fetchMarkersFromDb() async {
 await Future.delayed(Duration(seconds: Random().nextInt(7)));
 return [Random().nextInt(993) + 27, Random().nextInt(28) + 4];
}

Stream<List<int>> _timer() async* {
  while (true) {
    yield await _fetchMarkersFromDb();
    await Future.delayed(Duration(seconds: 5));
  }
}

void main() {
  _timer().listen(print);
}

and you could also add throttle/debounce from RxDart

Share:
1,572
Urmil Shroff
Author by

Urmil Shroff

Tech📱, cars🏎️, music🎧 and programming👨🏻‍💻

Updated on December 16, 2022

Comments

  • Urmil Shroff
    Urmil Shroff over 1 year

    I'm making a Flutter app where I need to periodically call a funtion every 5 seconds. Here is my current implementation:

    Timer.periodic(markerRefreshInterval, (Timer t) {
        print('$markerRefreshInterval seconds over, refreshing...');
        _fetchMarkersFromDb(); // updates markers every 5 seconds
    });
    

    However, sometimes (due to network issues, etc.) the _fetchMarkersFromDb() function takes longer than 5 seconds to execute. In such cases, I've figured out (with the help of debug prints in the console) that there are multiple instances (threads?) of the same function being executed parallely. This messes up a variable I'm trying to increment in each function call.

    I want _fetchMarkersFromDb() to be called at a minimum interval of 5 seconds, or the total function execution time + 5 seconds (if the function itself takes long to execute). I do not want it to run parallely like it does now. I've heard that Streams can be used, but I'm confused as to how to use them while calling the function repeatedly in an interval.

    Full source code can be found here, and the issue I'm trying to solve is here. Thanks in advance.