Progreso y cancelación
Un progreso honesto es más difícil de lo que parece, y una cancelación que de verdad para es más difícil todavía. Los tres modos de progreso, el patrón del testigo de cancelación, y qué hacer con el trabajo a medias.
Los tres modos de progreso
Y una regla de ritmo: no mandes un mensaje por elemento. Con diez mil ficheros, diez mil mensajes son diez mil cruces para pintar una barra que la pantalla solo puede refrescar sesenta veces por segundo. Manda cada cierto número de elementos o cada cierto tiempo, lo que ocurra antes. El nivel 21.4 lo trata a fondo.
La cancelación falsa
Es el patrón por defecto y hay que reconocerlo:
// El usuario pulsa cancelar…cancelado = true;progreso.onmessage = () => {}; // …y el frontend deja de escucharEl frontend deja de pintar, la interfaz responde, y el core sigue leyendo diez mil ficheros. La operación termina entera, gastando disco y CPU, y su resultado se descarta. Si el usuario cancela y vuelve a empezar, ahora hay dos.
Cancelar de verdad exige que el core se entere.
El testigo de cancelación
El patrón, con las tres piezas:
// 1 · un estado con los trabajos en marchapub struct Trabajos(pub Mutex<HashMap<TrabajoId, CancellationToken>>);// 2 · el comando registra su testigo y lo comprueba en el bucle#[tauri::command]async fn importar( trabajos: tauri::State<'_, Trabajos>, id: TrabajoId, ruta: PathBuf, progreso: Channel<Progreso>,) -> Result<Resumen, ErrorPayload> { let testigo = CancellationToken::new(); trabajos.0.lock().unwrap().insert(id, testigo.clone()); let mut hechos = 0usize; for item in leer(&ruta).await? { if testigo.is_cancelled() { // el trabajo a medias: ver la sección siguiente return Ok(Resumen::parcial(hechos)); } procesar(item).await?; hechos += 1; // …y el envío de progreso, con su ritmo } trabajos.0.lock().unwrap().remove(&id); Ok(Resumen::completo(hechos))}// 3 · otro comando lo cancela#[tauri::command]async fn cancelar(trabajos: tauri::State<'_, Trabajos>, id: TrabajoId) -> Result<(), ErrorPayload> { if let Some(t) = trabajos.0.lock().unwrap().get(&id) { t.cancel(); } Ok(())}Tres detalles que hacen que funcione:
- La comprobación va en el bucle, y su granularidad decide cuánto tarda en parar. Comprobar cada elemento es lo normal; si un elemento tarda mucho, hay que comprobar dentro.
- El cerrojo se toma y se suelta enseguida, sin cruzar el punto de espera. Es la regla del nivel 16.5.
- El comando devuelve, no falla. Cancelar no es un error: es un resultado. Devolver un resumen parcial deja al frontend en condiciones de contarlo bien.
El trabajo a medias
La pregunta que decide la calidad de la funcionalidad: ¿qué pasa con lo que ya se hizo?
Lo que no vale es la política implícita —lo que quede, queda— sin decírselo al usuario. Es la fuente de «he cancelado y me ha dejado la mitad de las cosas importadas», que es un fallo de comunicación más que técnico.