rmawatson / flutter_isolate

Launch an isolate that can use flutter plugins.
MIT License
262 stars 80 forks source link

Extremely slow in release mode in background for iOS and Android #130

Closed maxmitz closed 1 year ago

maxmitz commented 1 year ago

I used flutter_isolate to do computational exhausting operations in the background. I have a problem that for iOS and Android in release mode the operation more or less stops when I move it to the background.

Did I do something wrong or is the plugin not able to do this?

Thanks in advance and thanks for working on this plugin!

A small example to test it:

main.dart

import 'dart:isolate';

import 'package:flutter/material.dart';
import 'package:flutter_isolate/flutter_isolate.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool hasStarted = false;
  String? computationPercentage;
  FlutterIsolate? flutterIsolate;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(onPressed: (hasStarted) ? () {} : () async => triggerComputationalExaustingTask(), 
            child: const Text("Start computation")),
            if(computationPercentage != null) Text(computationPercentage!)
          ],
        ),
      ),
    );
  }
  Future<void> triggerComputationalExaustingTask() async {
    setState(() {
      hasStarted = true;
    });

    final port = ReceivePort();
    port.listen((message) { 
      if(message == stopIsolateMessage) {
        flutterIsolate?.kill();
        setState(() {
          hasStarted = false;
        });
      } else if (message is String){
        setState(() {
          computationPercentage = message;
        });
      }
    });

    flutterIsolate = await FlutterIsolate.spawn(computationalExaustingTask, port.sendPort);
  }
}

const stopIsolateMessage = "STOP_ISOLATE";

@pragma('vm:entry-point')
void computationalExaustingTask(SendPort sendPort) {
  sendPort.send("0%");
  double bigNumber = 10000000000;

  for (double i = 0; i < bigNumber; i++){
    i++;
    i--;
    if (i % (bigNumber/100) == 0) {
      sendPort.send("${i* 100~/bigNumber}%");
    }
  }

  sendPort.send("Finished");
  sendPort.send(stopIsolateMessage);
}
nmfisher commented 1 year ago

In the example you gave, what exactly happens - does the foreground/UI stop responding, or the isolate task never starts/completes, or the completion signal is never received on the main isolate?

maxmitz commented 1 year ago

This is not clear because it is not possible to set break points in the release mode. My guess is that isolate works extremely slowly in the background because it continues faster when you open the app again. My derivation from this is that the UI responses but a completion signal cannot be received because it did not finish.

maxmitz commented 1 year ago

I posted this in the flutter sdk as issue and I got the response that Isolates should be used for multithreading but not for background processes (https://github.com/flutter/flutter/issues/116715#issuecomment-1342702288). I think it is possible to close this issue.

nmfisher commented 1 year ago

Sorry I originally misunderstood what you meant by "background" (I misread and thought you were referring to work done in a background isolate while the app was still in the foreground, not while the app itself is backgrounded).

Code running in isolates is still considered "app code" by Android/iOS so you have no control over the priority allocated to the thread (in fact there are no guarantees that the app won't be killed entirely while in the background, which includes code running in isolates).

If you need to run long-running tasks while the app is in the background you will need to use background task (iOS) or work manager (Android). The referenced work_manager plugin for flutter is your best bet.