0

I have the following Dart Isolate called in my Flutter app that runs and handles a task via the _checkTimer method every 7 seconds. This is handy for background tasks that I need to run every certain period of time.

import 'dart:async';
import 'dart:isolate';

class TimeTrackingDigester with ChangeNotifier {
  Isolate _isolate;
  static int _counter = 0;
  ReceivePort _receivePort;

  void startIsolate() async {
    print('**** time tracking digester = startIsolate');

    _receivePort = ReceivePort();
    _isolate = await Isolate.spawn(
      _checkTimer,
      _receivePort.sendPort,
    );
    _receivePort.listen(
      _handleMessage,
      onDone: () {
        //@TODO this never fires
        print('done!');
      },
    );
  }

  void stopIsolate() => _stop();

  static void _checkTimer(SendPort sendPort) async {
    print('**** time tracking digestoer = checkTimer');
    Timer.periodic(
      const Duration(seconds: 7),
      (Timer t) async {
        _counter++;
        print('**** time tracking digestoer = $_counter');
        
        var task = 'result from some action here';

        sendPort.send(task);
      },
    );
  }

  void _handleMessage(dynamic data) {
    print('**** time tracking digestoer = handleMessage');
    print('RECEIVED: $data');
  }

  //@TODO this is not stopping the isolate
  void _stop() {
    print('**** time tracking digestoer = stop');
    print(_isolate);

    if (_isolate != null) {
      print('* isolate is not null');
      // setState(() {
      //     _running = false;
      //
      // });
      _receivePort.close();
      _isolate.kill(priority: Isolate.immediate);
      _isolate = null;
    }
  }
}

My first problem is once the isolate is started, I call stopIsolate method to stop the Isolate yet it doesn't ever hit the close()/kill() calls because the _isolate is considered null. From what I can gather, the _isolate is always null and I'm missing a step to actually set the _isolate in the following stanza:

if (_isolate != null) {
    print('* isolate is not null');
    // setState(() {
    //     _running = false;
    //
    // });
    _receivePort.close();
    _isolate.kill(priority: Isolate.immediate);
    _isolate = null;
 }

My second issue, is if I disregard the null check, and simply call the close()/kill() methods, the following Exceptions are thrown:

NoSuchMethodError: The method 'close' was called on null. I/flutter (15558): Receiver: null I/flutter (15558): Tried calling: close()

The end result I am trying to accomplish: when I want to stop the Isolate, it fully stops the subsequent _checkTimer method, and resolve the _isolate from not being set correctly in order to trigger the null check correctly.

Zach Smith
  • 5,490
  • 26
  • 84
  • 139
  • Don't have mush knowledge about isolates, but I think this can help, ifyou haven't already seen it. https://stackoverflow.com/a/63433061/10285344 – ASAD HAMEED Dec 28 '20 at 03:09

1 Answers1

0

I tested your code, and it can successfully stop the isolate. Please make sure that you're using the same instance of TimeTrackingDigester so that _isolate will not be null.

Run this sample app.

import 'dart:async';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(fontFamily: 'Roboto'),
      home: SampleIsolate(),
    );
  }
}

class SampleIsolate extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => TimeTrackingDigester(),
      child: Builder(
        builder: (context2) => Scaffold(
          body: Center(
            child: RaisedButton(
              onPressed: () {
                // Will get the TimeTrackingDigester created in "create: (_) => ..."
                final digester = Provider.of<TimeTrackingDigester>(
                  context2,
                  listen: false,
                );
                if (digester.isStarted) {
                  digester.stopIsolate();
                } else {
                  digester.startIsolate();
                }
              },
              child: const Text('Start / Stop'),
            ),
          ),
        ),
      ),
    );
  }
}

class TimeTrackingDigester with ChangeNotifier {
  Isolate _isolate;
  static int _counter = 0;
  ReceivePort _receivePort;

  bool get isStarted => _isolate != null;

  void startIsolate() async {
    print('**** time tracking digester = startIsolate');

    _receivePort = ReceivePort();
    _isolate = await Isolate.spawn(
      _checkTimer,
      _receivePort.sendPort,
    );
    _receivePort.listen(
      _handleMessage,
      onDone: () {
        //@TODO this never fires
        print('done!');
      },
    );
  }

  void stopIsolate() => _stop();

  static void _checkTimer(SendPort sendPort) async {
    print('**** time tracking digestoer = checkTimer');
    Timer.periodic(
      const Duration(seconds: 7),
      (Timer t) async {
        _counter++;
        print('**** time tracking digestoer = $_counter');

        var task = 'result from some action here';

        sendPort.send(task);
      },
    );
  }

  void _handleMessage(dynamic data) {
    print('**** time tracking digestoer = handleMessage');
    print('RECEIVED: $data');
  }

  //@TODO this is not stopping the isolate
  void _stop() {
    print('**** time tracking digestoer = stop');
    print(_isolate);

    if (_isolate != null) {
      print('* isolate is not null');
      // setState(() {
      //     _running = false;
      //
      // });
      _receivePort.close();
      _isolate.kill(priority: Isolate.immediate);
      _isolate = null;
    }
  }
}
rickimaru
  • 2,275
  • 9
  • 13
  • Please triple check that when you stop the isolate, the timer portion of the code also stops. I'm seeing the timer continue even when the isolate is stopped on my side. – Zach Smith Dec 28 '20 at 17:49
  • @ZachSmith Really? The timer stops on my side. I'm using iOS simulator (iPhone SE 1st gen 14.2), Dart version 2.10.4, and Flutter 1.22.5 (Channel stable). – rickimaru Dec 29 '20 at 02:13