Acotar la lectura atascada
Requisito
Lee cuatro valores de sonda en secuencia; la tercera lectura se atasca
durante 500 ms. Da a cada lectura un presupuesto de
150 ms: imprime las lecturas que llegan a tiempo, luego
reading timed out para el atasco, y
detente — la cuarta lectura no debe reportarse. El
atasco se inyecta de forma determinista en el código; las dos
versiones deben imprimir las líneas que aparecen bajo
Salida esperada.
Salida esperada
reading: 21.5 reading: 21.7 reading timed out
Lado a lado
RxDart
FxDart
Por qué difieren
El presupuesto por lectura en sí es fácil en ambos lados — el panel de
RxDart acota el Future dentro de asyncMap,
el timeout de FxDart acota el pull — así que la
diferencia interesante es qué mide el operador a nivel de
stream del mismo nombre en cada modelo.
Stream.timeout vigila el hueco entre
eventos: el productor decide cuándo llegan los valores, así
que «demasiado lento» solo puede significar «hace rato que no llega
nada». El timeout de FxDart acota el tiempo
demanda-a-elemento: el consumidor pregunta, y el
reloj corre desde la pregunta hasta la respuesta. En esta tarea finita
y secuencial ambos coincidirían — pero son cantidades genuinamente
distintas: un pipeline pull sin demanda no tiene huecos que medir, y
un stream push no le debe respuesta a la pregunta de nadie.
Cada lado necesita entonces un arruga real para cumplir la cláusula
«y detente». En el lado push la fuente atascada sigue ahí fuera, y
volvería a empujar lecturas en cuanto la lectura lenta por fin
aterrizara — así que después de que onErrorReturnWith
convierta el error en la línea del informe,
takeWhileInclusive termina el stream y cancela la
suscripción. En el lado pull detenerse es gratis — la
TimeoutException simplemente sale del bucle y nada
vuelve a tirar — pero conservar las lecturas que precedieron al
atasco significa recolectar con each en lugar de un
toList que las habría descartado al lanzar.
Un empate: un operador más una arruga en cada lado, y las arrugas son imágenes especulares de la naturaleza de cada modelo.
Benchmark
Caso async: la escala principal es N = 10,000, no 1,000,000. Cada elemento cuesta una vuelta del event loop en ambos lados, así que un millón de awaits reales mediría el event loop de Dart durante minutos — no el pipeline. Los retardos son de longitud cero y se conserva el límite de concurrencia del ejemplo; lo que comparan las barras es la maquinaria del pipeline.
N = 100
Tiempo Empate
Memoria pico Empate
N = 10,000
Tiempo Gana FxDart
Memoria pico Empate
Las barras son medianas de iteraciones cronometradas repetidas en procesos nuevos por lado (los N pequeños se agrupan por resolución del temporizador). Dos lados a menos del 5% entre sí — o a menos de 0.6 ms, una diferencia que nadie puede percibir — cuentan como empate; las carreras relativas ajustadas se vuelven a medir hasta 5 veces. En una app, cualquier cosa por debajo de unos pocos milisegundos es invisible para el usuario, gane la barra que gane. La memoria es el RSS pico del proceso. La VM de Dart y el dataset son idénticos en ambos lados, así que la diferencia entre las dos barras es lo que retiene el pipeline en sí.