Timer inside flutter isolate not stopping when isolate is killed

Issue

I have an app uploading joystick position data to a webserver using an API call.

This method gets called when a joystick is moved. It stops any previously running isolate and starts a new isolate if the joystick is not in the centre.

void onJoystickMoved(double angle, double distance) {
stopIsolate();
if(distance > 0.06){
  startIsolate(JoystickPosition.fromDistanceAndRadius(distance, angle));
}
}

The isolate start and stop methods

Future<void> startIsolate(JoystickPosition position) async {
   isolate = await Isolate.spawn(uploadJoystickPosition, position);
}

void stopIsolate() {
   if (isolate != null) {
     debugPrint("Stopping isolate");
     isolate.kill();
     isolate = null;
   }
}

uploadJoystickPosition method (the method in the isolate):

void uploadJoystickPosition(JoystickPosition position){

   Timer.periodic(new Duration(seconds: 1), (Timer t) {
      DataModel dataModel = DataModel(1, getTimeInSeconds());
      dataModel.joystickPosition = position;
      debugPrint("Distance: ${position.distance}");
      uploadData(dataModel).then(uploadResponse, onError: uploadError);
   });
}

Trouble is the uploadJoystickPosition keeps uploading old positions of the joystick along with new positions. I am assuming this is because the timer keeps running even when the isolate is killed.

Questions:

  1. Why does my timer keep going(and uploading) even after I kill the isolate?
  2. How do I get my timer to stop when I kill the isolate its running in?

Solution

As I noted in a comment, your example code has:

Future<void> startIsolate() async {
  stopIsolate();
  isolate =
      await Isolate.spawn(isolateMethod, DateTime.now().toIso8601String());
}

void stopIsolate() {
  if (isolate != null) {
    debugPrint("Stopping isolate");
    isolate.kill();
    isolate = null;
  }
}

Nothing stops startIsolate from being called while another call to startIsolate is already in progress. Therefore your problem is not that killing an isolate doesn’t stop its Timers, it’s that you leak isolates and prevent yourself from killing them. You need to add a guard to avoid spawning a new isolate while another creation request is in progress. A bool would be sufficient:

bool isStartingIsolate = false;

Future<void> startIsolate() async {
  if (isStartingIsolate) {
    // An isolate is already being spawned; no need to do anything.
    return;
  }

  stopIsolate();

  isStartingIsolate = true;
  try {
    isolate =
        await Isolate.spawn(isolateMethod, DateTime.now().toIso8601String());
  } finally {
    isStartingIsolate = false;
  }
}

A different approach, if you want to wait for the pending startIsolate call to finish before processing any new ones:

Future<void> pendingStartIsolate;

Future<void> startIsolate() async {
  while (pendingStartIsolate != null) {
    await pendingStartIsolate;
  }

  stopIsolate();

  try {
    pendingStartIsolate =
        Isolate.spawn(isolateMethod, DateTime.now().toIso8601String());
    isolate = await pendingStartIsolate;
  } finally {
    pendingStartIsolate = null;
  }
}

Answered By – jamesdlin

Answer Checked By – Mildred Charles (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *