UI se congela en simple computación asinc
Contexto
Estoy trabajando en una función que ejecutará algunas acciones cronológicamente y quiero ser capaz de pausar su ejecución y luego reanudar en una pulsación de botón.
Problema
Con el siguiente código, la interfaz de usuario se congela cuando el código llega al await Future.doWhile(() => _isTestPaused);
Código
import 'package:flutter/material.dart';
class Sample extends StatefulWidget {
const Sample();
@override
_SampleState createState() => _SampleState();
}
class _SampleState extends State {
String _text;
bool _isTestPaused;
@override
void initState() {
_text = '';
_isTestPaused = false;
WidgetsBinding.instance.addPostFrameCallback((_) => _run());
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Expanded(child: Text(_text)),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _isTestPaused = !_isTestPaused),
child: Icon(_isTestPaused ? Icons.play_arrow : Icons.pause),
),
);
}
Future _run() async {
int count = 0;
for (int i = 0; i < 100; i++) {
await Future.delayed(const Duration(seconds: 1));
await Future.doWhile(() => _isTestPaused);
setState(() => _text += '${count += 1}\n');
}
}
}
Pregunta hecha hace 3 años, 4 meses, 29 días - Por codercentaur
4 Respuestas:
-
Estás bloqueando tu código en
await Future.doWhile
De hecho estarías bloqueando incluso si no estuvieras usandoawait
Esto es porque el dardo es un solo hilo.Cuando llegas a un dardo de función asincrónica no crea un hilo separado, simplemente pasa la función, y cada vez que está listo cuenta el bucle del evento y se para en la parte posterior de la línea. Muy similar a cómo Nodejs maneja las cosas.
Puede abarcar un aislato para ejecutar operaciones costosas utilizando
compute
dentrodart:async
Pero no sé si puedes detener la ejecución de un bucle cambiando algún estado. Pero lo que sea que estés atando para hacer esto probablemente no es la manera correcta.Buena suerte.
Respondida el Dec 16, 2020 a las 16:22 - por bytebarricade
Votos positivos: 0 | Votos negativos: 0 -
Utilice un método Future para ejecutar el código async. Entonces llama a usar Future Builder. Dado que el desbordamiento es un solo hilo, llamando a async directamente desde dentro, puede hacer que la aplicación se congele.
Respondida el Dec 16, 2020 a las 16:27 - por scriptwarrior
Votos positivos: 0 | Votos negativos: 0 -
El problema que estás experimentando con la congelación de la interfaz de usuario se debe a que estás utilizando
Future.doWhile()
de forma sincrónica en tu función_run()
. Esto significa que mientras estás esperando a que_isTestPaused
cambie en el bucledoWhile
, estás bloqueando el hilo principal de Flutter, lo que provoca la congelación de la UI.Para solucionar este problema, puedes modificar tu función
_run()
para hacer un uso más eficiente de la asincronía, utilizando unFuture
que se resuelva cuando_isTestPaused
cambie. A continuación te muestro un ejemplo de cómo podrías refactorizar tu código para evitar que la UI se congele:dart Future
_run() async { int count = 0; for (int i = 0; i < 100; i++) { await Future.delayed(const Duration(seconds: 1)); // Cambia el bucle doWhile a un Future que se resuelve cuando _isTestPaused cambie await _waitForUnpaused(); setState(() { _text += '${count += 1}\n'; }); } } Future _waitForUnpaused() { if (!_isTestPaused) { return Future.value(); } Completer completer = Completer (); void onResume() { if (!_isTestPaused) { completer.complete(); } else { completer.complete(_waitForUnpaused()); } } _isTestPaused ? completer.future.then((_) => onResume()) : onResume(); return completer.future; } En este código, la función
_waitForUnpaused()
se encarga de esperar de forma asincrónica hasta que_isTestPaused
deje de sertrue
. Esto evita el bloqueo del hilo principal y permite que la UI siga siendo receptiva mientras la función_run()
espera a que se reanude la ejecución.Respondida el Dec 17, 2020 a las 16:19 - por Gpt
Votos positivos: 0 | Votos negativos: 0 -
El problema con el código proporcionado es que el
await Future.doWhile(() => _isTestPaused);
bloquea el hilo de la interfaz de usuario, lo que hace que la interfaz de usuario se congele hasta que se complete elFuture
. Para evitar este problema, puedes utilizar unStream
para escuchar los cambios en_isTestPaused
y pausar la ejecución cuando sea necesario. Aquí tienes un ejemplo de cómo puedes hacerlo:import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; class Sample extends StatefulWidget { const Sample(); @override _SampleState createState() => _SampleState(); } class _SampleState extends State
{ String _text; bool _isTestPaused; StreamSubscription _pauseSubscription; @override void initState() { _text = ''; _isTestPaused = false; WidgetsBinding.instance.addPostFrameCallback((_) => _run()); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Expanded(child: Text(_text)), ), floatingActionButton: FloatingActionButton( onPressed: () => setState(() => _isTestPaused = !_isTestPaused), child: Icon(_isTestPaused ? Icons.play_arrow : Icons.pause), ), ); } Future _run() async { int count = 0; Stream pauseStream = Stream.value(_isTestPaused).switchMap((_) => _isTestPaused ? Stream.periodic(const Duration(milliseconds: 100)) : Stream.value(false)); _pauseSubscription = pauseStream.listen((_) {}); for (int i = 0; i < 100; i++) { await Future.delayed(const Duration(seconds: 1)); await pauseStream.first; setState(() => _text += '${count += 1}\n'); } _pauseSubscription.cancel(); } @override void dispose() { _pauseSubscription.cancel(); super.dispose(); } } En este código, creamos un
Stream
(pauseStream
) que emite valores según el valor de_isTestPaused
. Cuando_isTestPaused
estrue
, elStream
emite valores cada 100 milisegundos, lo que hace que elawait pauseStream.first;
se pause hasta que_isTestPaused
se establezca enfalse
. Esto permite que la interfaz de usuario siga respondiendo mientras se pausa la ejecución.También es importante cancelar la suscripción al
Stream
en el métododispose
para evitar fugas de memoria.Respondida el Dec 17, 2020 a las 16:24 - por Gemini
Votos positivos: 0 | Votos negativos: 0