# Tema 3 · Bucle de eventos, colas y dispatching

Programación Dirigida por Eventos · Grado en Ingeniería Informática · 2026/27

Del publicar que llama en el acto a una cola en medio: productor, cola, bucle y handler; FIFO y prioridades con el panel de una caldera; run-to-completion, coalescencia, colas llenas, handlers largos y el Looper de Android.

## De la llamada directa a la cola

_La barra del bar en Kotlin (productor, cola, bucle y handler), run-to-completion y la ficha de traza del panel de una caldera recorrida con cola FIFO y con cola de prioridad._

### ¿Y si llegan tres eventos mientras el programa está ocupado? · Teoría

Hasta ahora, **quien producía un evento esperaba a que se atendiera**. Repasa cómo lo hemos ido viendo:

<div class="cards">
  <div class="card"><h4>U1 · Fuente, evento, receptor</h4><p>Un <code>while</code> que obtiene un evento y lo entrega. Solo tenía uno en la mano: para el siguiente había que esperar al teclado.</p></div>
  <div class="card"><h4>U2 · Callbacks y despacho</h4><p><code>publicar</code> llamaba en el acto a los handlers. Quien publicaba esperaba hasta que el último terminaba.</p></div>
  <div class="card"><h4>U3 · Una cola en medio</h4><p>Quien produce no espera: deja el evento y se va. Alguien lo atiende después, cuando le toque.</p></div>
</div>

### Lo que dejamos pendiente en la U2

En la clase 5 de la U2 arreglamos la reentrancia de la central de pedidos cambiando una sola palabra: `CentralConCola`. Su `publicar` ya llevaba dentro una cola y un `while`:

```kotlin
fun publicar(estado: String) {
    pendientes.addLast(estado)
    if (notificando) return
    notificando = true
    try {
        while (pendientes.isNotEmpty()) {
            val siguiente = pendientes.removeFirst()
            for (o in oyentes.toList()) {
                o(siguiente)
            }
        }
    } finally {
        notificando = false
    }
}
```

- `pendientes` guarda los avisos que llegan **durante** una ronda.
- La vacía **quien publicó primero**: se queda dentro hasta entregarlo todo.
- El `while` entrega los avisos de uno en uno: cada ronda termina antes de que empiece la siguiente.

La pregunta con la que cerramos la U2 era: *¿y si quien publica dejara el aviso y se fuera?* Esa es exactamente la idea de esta unidad.

> [!ANALOGIA] La barra del bar
> ¿Has esperado alguna vez en una barra donde el camarero no te toma nota hasta que termina de preparar lo del anterior? Eso es la llamada directa. Hoy ponemos una fila de comandas en medio.

**Preguntas**

**1.** En la U2, con un `publicar` que llama a los handlers en el acto, ¿cuándo puede seguir quien publica?

   a) En cuanto deja el evento en la cola
   b) Cuando el último handler ha terminado
   c) Cuando el primer handler ha empezado
   d) Nunca: publicar no devuelve el control

**2.** En `CentralConCola`, ¿quién vacía la cola `pendientes`?

   a) Un hilo aparte que la revisa cada cierto tiempo
   b) Cada suscriptor, cuando termina su aviso
   c) Quien publicó primero: se queda en el `while` hasta entregarlo todo
   d) Nadie: los avisos que llegan durante una ronda se descartan

### La barra del bar: cuatro piezas · Teoría

Una cola de eventos mínima tiene **cuatro papeles**. En la barra de un bar se ven muy bien:

<div class="flow">
  <div><b>Productor</b><small>quien pide</small></div>
  <div><b>Cola de eventos</b><small>la fila de comandas: entra por el final, sale por el principio</small></div>
  <div><b>Bucle</b><small>saca la primera y la entrega</small></div>
  <div><b>Handler</b><small>prepara UNA</small></div>
</div>

Quien pide **no espera** a que le preparen: deja la comanda y se va. Si en la cola hay `bocadillo`, `zumo` y `cafe` (el café llegó primero), lo siguiente que sale es el café, y el bocadillo, que entró el último, saldrá el último.

### Tres reglas

| Regla | En el bar |
| --- | --- |
| **1 · FIFO** | La primera comanda que entró es la primera que sale. |
| **2 · De una en una** | El camarero termina una comanda entera antes de coger la siguiente. |
| **3 · Se anota, no se prepara** | Si al preparar hace falta algo más, se anota al final de la fila; no se prepara en el acto. |

### Del bar al programa

Los mismos cuatro papeles, con otros nombres:

| En el bar | En el programa | En `Comandas.kt` |
| --- | --- | --- |
| Quien pide | Productor | `pedir(comanda)` |
| La fila de comandas | Cola de eventos | `comandas` (`ArrayDeque`) |
| El camarero que coge la primera y vuelve a mirar | Bucle de eventos | `atenderBarra()` |
| Preparar una comanda | Handler | `preparar(comanda)` |

> [!IMPORTANTE]
> El bucle no sabe qué es un café: **saca y entrega**. Todo lo que sabe hacer el bar está en el handler.

> [!EXAMEN]
> Saber poner nombre a las cuatro piezas en un ejemplo nuevo (productor, cola, bucle, handler) y enunciar las tres reglas es pregunta típica.

**Preguntas**

**3.** Ordena el recorrido de una comanda, desde que se pide hasta que se prepara.

   Ordena: El bucle la saca cuando es la primera · El handler la prepara · El productor (quien pide) deja la comanda · La comanda espera en la cola

**4.** En `Comandas.kt`, ¿qué función hace el papel de **bucle de eventos**? (solo el nombre)

**5.** Preparando un desayuno, el camarero ve que necesita un café. Según la regla 3, ¿qué hace?

   a) Prepara el café en el acto y luego sigue con el desayuno
   b) Anota el café al final de la fila
   c) Anota el café al principio de la fila para que salga el siguiente
   d) Deja el desayuno a medias y atiende la siguiente comanda

### PASO 1 · Llamada directa: pedir es preparar · Práctica

En clase lo tecleamos en vivo en **pde-consola**, en un fichero `Comandas.kt`. Empezamos por el punto de partida, como en la U2: quien pide llama directamente al handler.

```kotlin
fun preparar(comanda: String) {
    when (comanda) {
        else -> println("  preparado: $comanda")
    }
}

fun main() {
    println("PASO 1 - llamada directa")
    preparar("cafe")
    preparar("zumo")
}
```

Salida:

```text
PASO 1 - llamada directa
  preparado: cafe
  preparado: zumo
```

Dos cosas que hay que ver aquí:

1. **Pedir es preparar.** Quien llama a `preparar` espera a que termine. No hay forma de "dejar la comanda e irse".
2. **Igual que en la U2.** `publicar` llamaba al handler en el acto; aquí `main` llama a `preparar` en el acto.

El `when` con un solo `else` parece raro, pero está preparado para crecer: en el PASO 3 le añadiremos un caso especial.

> [!CUIDADO]
> Si el café tarda dos minutos, quien pide el zumo espera dos minutos. Con llamada directa, el ritmo lo marca el handler más lento, y quien produce queda bloqueado mientras tanto.

Si tienes pde-consola a mano, crea `Comandas.kt` con este código y ejecútalo: en los dos pasos siguientes lo iremos ampliando sin borrar nada.

**Preguntas**

**6.** Con este PASO 1, si preparar el café tardase dos minutos, ¿qué le pasaría a quien pide el zumo?

   a) Nada: el zumo se prepara a la vez que el café
   b) Esperaría esos dos minutos antes de poder pedir
   c) El zumo se perdería
   d) El zumo saldría antes porque es más rápido de preparar

**7.** En el PASO 1, ¿qué piezas de las cuatro **faltan**?

   a) El productor y el handler
   b) La cola y el bucle
   c) Solo el handler
   d) No falta ninguna

### PASO 2 · Una cola y un bucle · Práctica

Encima de lo anterior añadimos las tres piezas que faltaban: **la cola, quien anota y quien atiende**.

```kotlin
val comandas = ArrayDeque<String>()

fun pedir(comanda: String) {
    comandas.addLast(comanda)
    println("  anotado: $comanda (en cola: ${comandas.size})")
}

fun atenderBarra() {
    while (comandas.isNotEmpty()) {
        val comanda = comandas.removeFirst()
        preparar(comanda)
    }
    println("  (cola vacia: el bucle se queda esperando)")
}

fun main() {
    println("PASO 2 - cola y bucle")
    pedir("cafe")
    pedir("zumo")
    pedir("bocadillo")
    atenderBarra()
}
```

- **La cola** (`comandas`): lo pedido y todavía no preparado.
- **Entra por el final** (`addLast`): quien pide anota y se va.
- **Sale por el principio** (`removeFirst`): la más antigua primero. Eso es **FIFO**.

`atenderBarra` no decide nada: mientras haya comandas, saca la más antigua y se la entrega al handler.

Salida:

```text
PASO 2 - cola y bucle
  anotado: cafe (en cola: 1)
  anotado: zumo (en cola: 2)
  anotado: bocadillo (en cola: 3)
  preparado: cafe
  preparado: zumo
  preparado: bocadillo
  (cola vacia: el bucle se queda esperando)
```

1. **Primero se anota todo**: las tres líneas de «anotado» salen antes que ningún «preparado». Nadie ha esperado.
2. **Después, en orden de llegada**: se prepara en el mismo orden en que se pidió.
3. **Y el bucle se queda sin trabajo**: aquí termina porque es un programa de consola.

> [!NOTA]
> Un bucle de verdad (el de Android, el de un navegador, el de un firmware) **no termina**: se queda dormido hasta que llega algo.

**Preguntas**

**8.** ¿Qué llamada sobre el `ArrayDeque` hace que salga siempre la comanda más antigua? (solo el nombre del método)

**9.** Si en `atenderBarra` cambiases `removeFirst()` por `removeLast()`, ¿en qué orden saldrían los «preparado»?

   a) cafe, zumo, bocadillo
   b) bocadillo, zumo, cafe
   c) zumo, cafe, bocadillo
   d) Solo saldría bocadillo

### PASO 3 · Un handler que encola (predice antes de ejecutar) · Ejercicio

Un **desayuno** es un café y una tostada. Cambiamos el handler para que, al recibir un desayuno, **pida** las dos cosas:

```kotlin
fun preparar(comanda: String) {
    when (comanda) {
        "desayuno" -> {
            pedir("cafe")
            pedir("tostada")
            println("  desayuno: repartido en dos comandas")
        }
        else -> println("  preparado: $comanda")
    }
}

// en main
pedir("desayuno")
pedir("zumo")
atenderBarra()
```

> [!IMPORTANTE]
> Fíjate: el handler del desayuno llama a `pedir`, **no** a `preparar`. No prepara el café: lo anota.

### Antes de ejecutar

Hay un zumo esperando detrás del desayuno. En clase se votó a mano alzada: **¿qué sale antes, el café del desayuno o el zumo?**

Recorre la cola a mano. Te ayuda apuntar en un papel cómo queda `comandas` después de cada línea:

| Momento | Cola después |
| --- | --- |
| `pedir("desayuno")` | `[desayuno]` |
| `pedir("zumo")` | `[desayuno, zumo]` |
| el bucle saca el desayuno | ? |
| el handler pide café y tostada | ? |
| ... | ... |

Pista para el número «en cola»: cuando el handler del desayuno empieza, el desayuno **ya ha salido** de la cola.

No sigas hasta tener tu predicción completa: en el paso siguiente verás la salida real.

**Preguntas**

**10.** ¿Qué se prepara antes?

   a) El café del desayuno
   b) El zumo
   c) Salen a la vez
   d) Depende de cuál tarde menos

**11.** Ordena las líneas que imprime el PASO 3 (sin la cabecera).

   Ordena: (cola vacia: el bucle se queda esperando) · anotado: cafe (en cola: 2) · anotado: desayuno (en cola: 1) · anotado: tostada (en cola: 3) · anotado: zumo (en cola: 2) · desayuno: repartido en dos comandas · preparado: cafe · preparado: tostada · preparado: zumo

### Sale antes el zumo: run-to-completion · Teoría

Esta es la salida real del PASO 3:

```text
PASO 3 - un handler que encola
  anotado: desayuno (en cola: 1)
  anotado: zumo (en cola: 2)
  anotado: cafe (en cola: 2)
  anotado: tostada (en cola: 3)
  desayuno: repartido en dos comandas
  preparado: zumo
  preparado: cafe
  preparado: tostada
  (cola vacia: el bucle se queda esperando)
```

1. **El café y la tostada van al final.** Se anotan detrás del zumo, que ya estaba en la cola.
2. **El handler termina entero.** «desayuno: repartido…» sale antes que cualquier «preparado»: solo cuando el handler del desayuno acaba, el bucle coge la comanda siguiente.
3. **Con llamada directa, al revés.** Si el handler llamase a `preparar("cafe")`, el café se habría preparado *dentro* del desayuno, antes que el zumo.

> [!IMPORTANTE] Run-to-completion
> Un handler **nunca es interrumpido por otro**. Lo que produce mientras se ejecuta espera su turno en la cola.

Esto es lo que diferencia de verdad la cola de la llamada directa: con cola, cada evento se atiende **de principio a fin** y los eventos nuevos **no se cuelan**. El orden de ejecución pasa a depender solo del orden en que las cosas entran en la cola.

![Dos filas comparan el handler del desayuno. Arriba, con llamada directa: dentro de la caja preparar(desayuno) se ejecutan preparar(cafe) y preparar(tostada), y el zumo va después. Abajo, con cola y bucle: preparar(desayuno) termina entero tras anotar café y tostada, luego se prepara el zumo y después el café y la tostada, que se anotaron al final de la cola](/img/pde/tema-3/llamada-vs-cola.svg "Llamada directa: el café se prepara dentro del desayuno. Con cola: se anota al final y espera detrás del zumo")

<div class="cards">
  <div class="card"><h4>Llamada directa</h4><p>Un handler dentro de otro: el café se prepara mientras el desayuno sigue a medias.</p></div>
  <div class="card"><h4>Cola + bucle</h4><p>El desayuno termina; el café espera detrás del zumo.</p></div>
</div>

> [!EXAMEN]
> «¿Qué imprime?» con un handler que encola es un clásico. La clave: lo encolado por un handler va **detrás de todo lo que ya esperaba** y no empieza hasta que ese handler termina.

**Preguntas**

**12.** Si el handler del desayuno llamase a `preparar("cafe")` en lugar de `pedir("cafe")`, ¿qué se imprimiría antes?

   a) «preparado: zumo»
   b) «preparado: cafe»
   c) «desayuno: repartido en dos comandas»
   d) «anotado: cafe»

**13.** Marca las afirmaciones correctas sobre run-to-completion.

   a) Un handler se ejecuta entero antes de que el bucle saque el siguiente evento
   b) Un evento encolado por un handler puede interrumpirlo si es urgente
   c) Lo que produce un handler espera su turno en la cola
   d) Garantiza que todos los eventos se atienden en el mismo instante en que ocurren

### El panel de una caldera (Anexo 0) · Teoría

Para la Actividad 1 cambiamos de escenario: el **panel de una caldera** recibe tres tipos de evento. Cada uno tiene una notación corta (la que usaremos en la ficha), una prioridad y la línea que muestra el handler al atenderlo.

| Evento | Notación | Prioridad | Línea que muestra el handler |
| --- | --- | --- | --- |
| `Pulsacion(boton)` | `P:SUBIR` | NORMAL | Pulsado SUBIR |
| `Lectura(grados)` | `L21` | BAJA | Lectura 21 grados |
| `Emergencia(motivo)` | `E:humo` | ALTA | EMERGENCIA: humo |

`L21` es una lectura de 21 grados, `P:SUBIR` una pulsación del botón SUBIR y `E:humo` una emergencia por humo. En la ficha, **el primero de la lista es el siguiente en salir**.

> [!IMPORTANTE] Regla del handler
> Al atender una lectura de **30 grados o más**, el handler **publica** una `Emergencia("temperatura N")`. No la atiende él: la publica. (En la ficha se puede abreviar `E:t31`.)

El handler, tal como está en `PanelEventos.kt`:

```kotlin
fun manejarPanel(evento: EventoPanel, publicar: (EventoPanel) -> Unit): String =
    when (evento) {
        is Pulsacion -> "Pulsado ${evento.boton}"
        is Lectura -> {
            if (evento.grados >= 30) publicar(Emergencia("temperatura ${evento.grados}"))
            "Lectura ${evento.grados} grados"
        }
        is Emergencia -> "EMERGENCIA: ${evento.motivo}"
    }
```

Recibe un evento y **devuelve** la línea que muestra el panel. El parámetro `publicar` es una **función**: es la manera que tiene el handler de producir un evento nuevo sin atenderlo él.

Y las prioridades:

```kotlin
enum class PrioridadPanel { ALTA, NORMAL, BAJA } // cuanto más arriba, antes se atiende

fun prioridadDelEvento(evento: EventoPanel): PrioridadPanel =
    when (evento) {
        is Emergencia -> PrioridadPanel.ALTA
        is Pulsacion -> PrioridadPanel.NORMAL
        is Lectura -> PrioridadPanel.BAJA
    }
```

**Preguntas**

**14.** ¿Qué línea devuelve `manejarPanel` al atender `L31`?

**15.** Según `prioridadDelEvento`, ¿qué prioridad tiene una pulsación?

   a) ALTA
   b) NORMAL
   c) BAJA
   d) Depende del botón

**16.** ¿Qué es el parámetro `publicar` de `manejarPanel`?

   a) Un booleano que indica si hay que imprimir la línea
   b) Una función que el handler llama para producir un evento nuevo
   c) La cola de eventos
   d) La prioridad del evento

### Sin cola, la emergencia sale antes que su lectura · Ejercicio

Primero lo vemos sin cola. En `PanelDirecto.kt` cada evento se entrega al handler **en el acto**: quien produce el evento llama al handler y espera. Con los eventos del guion de la actividad, la salida es:

```text
Panel con llamada directa (sin cola)
  Lectura 21 grados
  Pulsado SUBIR
  EMERGENCIA: temperatura 31
  Lectura 31 grados
  Pulsado MODO
  EMERGENCIA: humo
```

Tres cosas que observar:

1. **Entrega en el acto.** Los eventos salen exactamente en el orden en que se producen; no hay nada que reordenar.
2. **La emergencia sale antes.** «EMERGENCIA: temperatura 31» aparece **antes** que la «Lectura 31 grados» que la provoca.
3. **Un handler dentro de otro.** El handler de la emergencia se ejecuta *dentro* del de la lectura: `manejarPanel(L31)` llama a `publicar`, y con llamada directa `publicar` atiende la emergencia en el acto. La línea de la emergencia sale y, cuando vuelve, la lectura termina y devuelve su propia línea.

Es el mismo fenómeno que la reentrancia de la U2: el orden de la salida no coincide con el orden lógico de los hechos (primero se lee 31 grados, luego hay emergencia).

> [!NOTA]
> Ahora pondrás una cola en medio, en papel. ¿Dónde saldrá esa emergencia?

**Preguntas**

**17.** ¿Por qué en `PanelDirecto.kt` «EMERGENCIA: temperatura 31» sale antes que «Lectura 31 grados»?

   a) Porque las emergencias tienen prioridad ALTA
   b) Porque `publicar` atiende la emergencia en el acto, antes de que el handler de la lectura devuelva su línea
   c) Porque el sensor envía la emergencia antes que la lectura
   d) Porque la cola FIFO la coloca delante

**18.** En el momento en que se imprime «EMERGENCIA: temperatura 31», ¿cuántas ejecuciones de `manejarPanel` hay en marcha a la vez? (un número)

### Actividad 1 · Parte A: cola FIFO · Ejercicio

En clase, en pareja y en papel: **9 minutos con la cola FIFO** y 8 con la de prioridad (Anexo 1, ficha de traza).

### El guion: 11 pasos

| Paso | Operación |
| --- | --- |
| 1 a 3 | encolar `L21`, `P:SUBIR`, `L31` |
| 4 | atender uno |
| 5 y 6 | encolar `P:MODO`, `E:humo` |
| 7 a 11 | atender uno, hasta vaciar la cola |

En cada fila escribe **cómo queda la cola después del paso** (el primero de la lista es el siguiente en salir) y, si se atiende un evento, **qué línea sale**. En «atender uno» se atiende **uno solo**. No olvides la regla del handler: una lectura de 30 grados o más publica una emergencia (`E:t31`).

**Parte A · Cola FIFO**: se atiende en orden de llegada.

| Paso | Operación | Cola después | Línea que sale |
| --- | --- | --- | --- |
| 1 | encolar L21 | `[L21]` | |
| 2 | encolar P:SUBIR | | |
| 3 | encolar L31 | | |
| 4 | atender uno | | |
| 5 | encolar P:MODO | | |
| 6 | encolar E:humo | | |
| 7–11 | atender uno | | |

Rellénala a mano y **después** compruébala con el simulador (modo FIFO, paso a paso):

> _Aquí hay un simulador interactivo (`cola-panel`): ábrelo en la web._
> [!TRUCO]
> El evento que publica un handler entra en la cola **en el paso en que se atiende su lectura**, y va al final.

**Preguntas**

**19.** Con la cola FIFO, ordena los eventos según se atienden.

   Ordena: E:humo · E:t31 · L21 · L31 · P:MODO · P:SUBIR

**20.** **A1.** ¿En qué puesto se atiende `E:humo` con la cola FIFO? (un número)

**21.** **A1 (bis).** Cuando `E:humo` llega a la cola (paso 6), ¿cuántos eventos tiene delante?

### Parte A resuelta y preguntas A2 y A3 · Ejercicio

Compara tu ficha con la solución de la Parte A:

| Paso | Operación | Cola después | Línea que sale |
| --- | --- | --- | --- |
| 1 | encolar L21 | `[L21]` | — |
| 2 | encolar P:SUBIR | `[L21, P:SUBIR]` | — |
| 3 | encolar L31 | `[L21, P:SUBIR, L31]` | — |
| 4 | atender uno | `[P:SUBIR, L31]` | Lectura 21 grados |
| 5 | encolar P:MODO | `[P:SUBIR, L31, P:MODO]` | — |
| 6 | encolar E:humo | `[P:SUBIR, L31, P:MODO, E:humo]` | — |
| 7 | atender uno | `[L31, P:MODO, E:humo]` | Pulsado SUBIR |
| 8 | atender uno | `[P:MODO, E:humo, E:t31]` | Lectura 31 grados |
| 9 | atender uno | `[E:humo, E:t31]` | Pulsado MODO |
| 10 | atender uno | `[E:t31]` | EMERGENCIA: humo |
| 11 | atender uno | `[]` | EMERGENCIA: temperatura 31 |

Fíjate en la fila 8: al atender `L31`, la cola **pierde** `L31` y **gana** `E:t31` al final. La línea que sale es la de la lectura; la emergencia tendrá que esperar a que salgan los que ya estaban (`P:MODO` y `E:humo`).

> [!CUIDADO] Errores frecuentes en la ficha
> - Atender dos eventos en un mismo «atender uno».
> - Poner `E:t31` en el paso 3, cuando se encola `L31`. Encolar una lectura no ejecuta su handler: la emergencia nace al **atenderla**.
> - Poner `E:t31` al principio de la cola «porque es una emergencia». En FIFO no hay prioridades.

Compara también con `PanelDirecto.kt`: allí la emergencia de temperatura salía **antes** que su lectura; aquí sale **la última**, después de su lectura.

**Preguntas**

**22.** **A2.** ¿En qué paso entra en la cola `E:temperatura 31` y quién la encola?

   a) En el paso 3, la encola quien encola `L31`
   b) En el paso 8, la encola el handler de `L31` (a través de `publicar`)
   c) En el paso 8, la encola el bucle porque detecta los 31 grados
   d) En el paso 11, la encola el handler de `E:humo`

**23.** **A3.** En `PanelDirecto.kt` esa emergencia salía antes que su lectura. ¿Por qué con cola sale después?

   a) Porque la cola FIFO da menos prioridad a las emergencias
   b) Porque el handler de la lectura publica (encola) la emergencia y termina; la emergencia espera su turno en la cola
   c) Porque la cola invierte el orden de los eventos
   d) Porque con cola el handler ya no publica la emergencia

### Actividad 1 · Parte B: cola con prioridad · Ejercicio

Mismo guion, mismos eventos, mismo bucle y mismo handler. Lo único que cambia es **la cola**.

**Parte B · Cola con prioridad**: **ALTA** antes que **NORMAL**, NORMAL antes que **BAJA**. **Entre iguales, orden de llegada.**

| Prioridad | Eventos |
| --- | --- |
| ALTA | `E:humo`, `E:t31` |
| NORMAL | `P:SUBIR`, `P:MODO` |
| BAJA | `L21`, `L31` |

Cómo se trabaja en la ficha: al encolar, el evento **se coloca delante del primero que sea menos urgente que él**; si hay otros de su misma prioridad, se queda detrás de ellos. «Atender uno» saca siempre el primero de la lista.

| Paso | Operación | Cola después | Línea que sale |
| --- | --- | --- | --- |
| 1 | encolar L21 | `[L21]` | |
| 2 | encolar P:SUBIR | | |
| 3 | encolar L31 | | |
| 4 | atender uno | | |
| 5 | encolar P:MODO | | |
| 6 | encolar E:humo | | |
| 7–11 | atender uno | | |

Rellena la columna y comprueba con el simulador, ahora en modo prioridad:

> _Aquí hay un simulador interactivo (`cola-panel`): ábrelo en la web._
> [!TRUCO]
> En el paso 4 ya se nota la diferencia: hay una lectura que llegó antes, pero ¿es la más urgente?

**Preguntas**

**24.** Con la cola con prioridad, ordena los eventos según se atienden.

   Ordena: E:humo · E:t31 · L21 · L31 · P:MODO · P:SUBIR

**25.** **B1.** ¿En qué puesto se atiende ahora `E:humo`? (un número)

### Parte B resuelta y preguntas B2 a B4 · Ejercicio

Solución de la Parte B:

| Paso | Operación | Cola después | Línea que sale |
| --- | --- | --- | --- |
| 1 | encolar L21 | `[L21]` | — |
| 2 | encolar P:SUBIR | `[P:SUBIR, L21]` | — |
| 3 | encolar L31 | `[P:SUBIR, L21, L31]` | — |
| 4 | atender uno | `[L21, L31]` | Pulsado SUBIR |
| 5 | encolar P:MODO | `[P:MODO, L21, L31]` | — |
| 6 | encolar E:humo | `[E:humo, P:MODO, L21, L31]` | — |
| 7 | atender uno | `[P:MODO, L21, L31]` | EMERGENCIA: humo |
| 8 | atender uno | `[L21, L31]` | Pulsado MODO |
| 9 | atender uno | `[L31]` | Lectura 21 grados |
| 10 | atender uno | `[E:t31]` | Lectura 31 grados |
| 11 | atender uno | `[]` | EMERGENCIA: temperatura 31 |

### Mismos eventos, otra cola, otro orden

| Cola | Orden de atención |
| --- | --- |
| FIFO | L21, P:SUBIR, L31, P:MODO, E:humo, E:temperatura 31 |
| Prioridad | P:SUBIR, E:humo, P:MODO, L21, L31, E:temperatura 31 |

<div class="cards">
  <div class="card"><h4>E:humo</h4><p>Con FIFO, la quinta: esperó detrás de tres. Con prioridad, la segunda.</p></div>
  <div class="card"><h4>L21</h4><p>Alguien lo paga: llegó la primera y con prioridad se atiende la cuarta.</p></div>
  <div class="card"><h4>E:temperatura 31</h4><p>La última en las dos colas, siempre después de su lectura.</p></div>
</div>

> [!IMPORTANTE]
> El bucle y el handler no han cambiado. **El orden lo decide la cola, no el bucle.**

> [!EXAMEN]
> Prioridad **no es** interrupción: la emergencia adelanta en la cola, pero no corta al handler que se esté ejecutando.

**Preguntas**

**26.** **B2.** ¿Qué evento sale perdiendo respecto a la Parte A? (notación de la ficha)

**27.** **B3.** Cuando llega `E:humo`, ¿interrumpe al handler de algún otro evento?

   a) Sí, interrumpe al handler de P:SUBIR
   b) Sí, interrumpe al handler de cualquier evento de prioridad menor
   c) No: adelanta en la cola, pero ningún handler se interrumpe (run-to-completion)
   d) No, porque se descarta hasta que la cola se vacía

**28.** **B4.** Si no dejaran de llegar pulsaciones, ¿cuándo se atendería `L21`?

   a) Al cabo de 11 pasos, como en el guion
   b) En cuanto fuese la más antigua de la cola
   c) Nunca: siempre habría algo más urgente delante (inanición)
   d) Inmediatamente, porque las lecturas tienen la prioridad más alta

### Cierre: tres ideas y una pregunta para la Hora 2 · Teoría

### Tres ideas que te llevas

<div class="cards">
  <div class="card"><h4>1 · La cola separa</h4><p>...cuándo <b>ocurre</b> un evento de cuándo <b>se atiende</b>. Quien produce deja el evento y sigue.</p></div>
  <div class="card"><h4>2 · De uno en uno y hasta el final</h4><p>El bucle saca un evento, el handler lo termina entero (run-to-completion) y solo entonces se saca el siguiente.</p></div>
  <div class="card"><h4>3 · El orden lo decide la cola</h4><p>Mismo bucle y mismo handler: con FIFO o con prioridad, el orden de atención cambia.</p></div>
</div>

### La ficha es la especificación de la Hora 2

La ficha rellena es tuya: guárdala. En la Hora 2 recibes el programa completo y **imprime lo que hoy has escrito a mano**. Antes de la Hora 2:

- Copia en tu pde-consola los cuatro archivos de `TRAB_U3-1_alumno.zip`.
- Comprueba que `ColaPanel.kt` se ejecuta.

### Para pensar

El handler `manejarPanel` recibe un parámetro que se llama `publicar`. **¿Qué le pasa el bucle ahí?** Búscalo en `ColaPanel.kt`.

Piensa en lo que has visto hoy: en `PanelDirecto.kt`, publicar una emergencia equivalía a atenderla en el acto (un handler dentro de otro). En la ficha, en cambio, la emergencia **entraba en la cola** y esperaba. Lo que el handler recibe en `publicar` es lo que decide cuál de los dos comportamientos tienes, sin cambiar ni una línea del handler.

> [!NOTA]
> Es el mismo truco que la inversión de control de la U2: el handler no sabe a quién avisa; se lo inyecta quien lo llama.

**Preguntas**

**29.** Con cola, ¿qué tiene que pasarle el bucle a `manejarPanel` como `publicar` para que la emergencia salga después de su lectura?

   a) Una función que llama a `manejarPanel` con el evento nuevo
   b) Una función que mete el evento nuevo en la cola
   c) Una función que imprime el evento nuevo con `println`
   d) Una función vacía que ignora el evento

**30.** ¿Cuáles son las tres ideas de la sesión?

   a) La cola separa cuándo ocurre un evento de cuándo se atiende
   b) El bucle atiende de uno en uno y hasta el final
   c) Un evento urgente interrumpe al handler en curso
   d) El orden lo decide la cola
   e) El orden lo decide el bucle

### Repaso: de la llamada directa a la cola · Repaso

> [!RESUMEN] Chuleta
> - **Llamada directa**: producir = atender. Quien produce espera, y un handler puede ejecutarse dentro de otro.
> - **Cuatro piezas**: productor (`pedir`), cola (`ArrayDeque`), bucle (`atenderBarra`), handler (`preparar`).
> - **FIFO** = `addLast` + `removeFirst`.
> - **Run-to-completion**: el handler termina entero; lo que produce va a la cola y espera su turno.
> - **Handler que encola**: lo nuevo va **detrás** de todo lo que ya esperaba.
> - **Prioridad**: ALTA > NORMAL > BAJA; entre iguales, orden de llegada. Adelanta en la cola, **no interrumpe**.
> - **Inanición**: si lo urgente no deja de llegar, lo menos urgente no se atiende nunca.
> - **El orden lo decide la cola**, no el bucle.

| Situación | Dónde sale el evento publicado por un handler |
| --- | --- |
| Llamada directa | Dentro del handler que lo publica (antes que la línea de este) |
| Cola FIFO | Al final de la cola, después de todo lo que esperaba |
| Cola con prioridad | Delante de los menos urgentes, pero siempre después de que termine el handler actual |

Responde como si fuera el parcial.

**Preguntas**

**31.** Cola FIFO del panel con `[P:MODO, L35]`. Se atiende uno y llega `E:humo`. ¿Cómo queda la cola?

   a) `[E:humo, L35]`
   b) `[L35, E:humo]`
   c) `[L35, E:t35, E:humo]`
   d) `[E:humo, L35, E:t35]`

**32.** Cola con prioridad vacía. Se encolan, por este orden, `L40`, `P:SUBIR` y `E:humo`, y después se atienden los tres. ¿Qué evento se atiende el **último**?

**33.** Marca las afirmaciones correctas.

   a) Con cola, quien produce un evento no espera a que se atienda
   b) Con cola con prioridad, una emergencia corta el handler de una lectura en curso
   c) Si un handler encola un evento, ese evento no empieza hasta que el handler termina
   d) Cambiar FIFO por prioridad exige reescribir el handler

### Solucionario

1. b) Cuando el último handler ha terminado  
   Llamada directa = esperar. `publicar` recorre los handlers y solo devuelve el control cuando el último ha terminado. Dejar el evento y seguir es justo lo que aporta la cola.
2. c) Quien publicó primero: se queda en el `while` hasta entregarlo todo  
   El primero que publica pone `notificando = true` y entra en el `while`. Los que publican durante la ronda encolan y se van (`return`). En la U3 damos el paso siguiente: que **nadie** que publique se quede a vaciar la cola, sino un bucle dedicado.
3. 1. El productor (quien pide) deja la comanda → 2. La comanda espera en la cola → 3. El bucle la saca cuando es la primera → 4. El handler la prepara  
   Productor → cola → bucle → handler. El productor no espera a nadie: deja la comanda y se va.
4. `atenderBarra`  
   `atenderBarra()` es el bucle: mientras haya comandas, saca la más antigua y se la da a `preparar`, que es el handler. `pedir` es el productor y `comandas` la cola.
5. b) Anota el café al final de la fila  
   Lo que haga falta mientras se prepara va **al final** de la fila. Prepararlo en el acto sería volver a la llamada directa; dejar el desayuno a medias rompería la regla 2 (de una en una).
6. b) Esperaría esos dos minutos antes de poder pedir  
   Con llamada directa todo es secuencial y bloqueante: `preparar("zumo")` no empieza hasta que `preparar("cafe")` devuelve el control.
7. b) La cola y el bucle  
   Hay quien pide (`main`) y quien prepara (`preparar`), pero no hay **cola** donde dejar la comanda ni **bucle** que la saque. Es lo que añade el PASO 2.
8. `removeFirst`  
   `addLast` mete por el final y `removeFirst` saca por el principio: la combinación da FIFO. Si sacases con `removeLast` tendrías una pila (LIFO).
9. b) bocadillo, zumo, cafe  
   Con `removeLast` la estructura se comporta como una pila (LIFO): sale primero lo último que entró. El bucle seguiría igual; lo que cambia el orden es **la cola**, no el bucle.
10. b) El zumo  
   El café se anota **al final** de la cola, detrás del zumo, que ya estaba esperando. Así que se prepara antes el zumo.
11. 1. anotado: desayuno (en cola: 1) → 2. anotado: zumo (en cola: 2) → 3. anotado: cafe (en cola: 2) → 4. anotado: tostada (en cola: 3) → 5. desayuno: repartido en dos comandas → 6. preparado: zumo → 7. preparado: cafe → 8. preparado: tostada → 9. (cola vacia: el bucle se queda esperando)  
   El handler del desayuno se ejecuta **entero** (anota café, anota tostada, imprime su línea) y solo entonces el bucle coge la siguiente comanda, que es el zumo. Café y tostada quedaron detrás.
12. b) «preparado: cafe»  
   Con llamada directa el café se prepara dentro del desayuno: «preparado: cafe» sale antes que la línea del desayuno y antes que el zumo. Un handler dentro de otro.
13. a) Un handler se ejecuta entero antes de que el bucle saque el siguiente evento; c) Lo que produce un handler espera su turno en la cola  
   Nada interrumpe al handler que se está ejecutando, ni siquiera algo urgente; lo nuevo va a la cola. Y precisamente por eso los eventos **no** se atienden cuando ocurren, sino cuando les toca.
14. `Lectura 31 grados`  
   Devuelve «Lectura 31 grados». Además, como 31 ≥ 30, antes de devolverla llama a `publicar(Emergencia("temperatura 31"))`: produce la emergencia, pero no la atiende.
15. b) NORMAL  
   Emergencia → ALTA, Pulsacion → NORMAL, Lectura → BAJA. El botón no influye.
16. b) Una función que el handler llama para producir un evento nuevo  
   Su tipo es `(EventoPanel) -> Unit`: una función que recibe un evento. El handler no sabe qué hace esa función por dentro; eso lo decide quien le llama (el bucle).
17. b) Porque `publicar` atiende la emergencia en el acto, antes de que el handler de la lectura devuelva su línea  
   No hay cola ni prioridad que valga: `manejarPanel(L31)` llama a `publicar` **antes** de devolver «Lectura 31 grados», y esa llamada ejecuta entero el handler de la emergencia. Un handler dentro de otro.
18. `2`  
   Dos: la de la lectura (a medias, esperando a que `publicar` vuelva) y la de la emergencia. Con cola esto nunca pasa: run-to-completion garantiza que solo hay una en marcha.
19. 1. L21 → 2. P:SUBIR → 3. L31 → 4. P:MODO → 5. E:humo → 6. E:t31  
   FIFO: L21, P:SUBIR, L31, P:MODO, E:humo, E:t31. La emergencia de temperatura entra al final cuando se atiende L31 (paso 8), detrás de P:MODO y E:humo.
20. `5`  
   Se atiende la **quinta**, en el paso 10. Una emergencia, esperando detrás de pulsaciones y lecturas.
21. `3`  
   Tres: `P:SUBIR`, `L31` y `P:MODO`. La cola tras el paso 6 es `[P:SUBIR, L31, P:MODO, E:humo]`.
22. b) En el paso 8, la encola el handler de `L31` (a través de `publicar`)  
   Entra en el paso 8, cuando se **atiende** `L31`. La encola el handler de la lectura llamando a `publicar`. El bucle no sabe nada de grados: solo saca y entrega.
23. b) Porque el handler de la lectura publica (encola) la emergencia y termina; la emergencia espera su turno en la cola  
   Con cola, `publicar` no atiende: **encola**. El handler de `L31` termina entero y devuelve «Lectura 31 grados»; la emergencia espera en la cola y sale cuando le toca. Ya no hay un handler dentro de otro.
24. 1. P:SUBIR → 2. E:humo → 3. P:MODO → 4. L21 → 5. L31 → 6. E:t31  
   Prioridad: P:SUBIR (paso 4, la más urgente de las que había), E:humo, P:MODO, L21, L31 y E:t31. La emergencia de temperatura nace en el paso 10 al atender L31 y es lo único que queda.
25. `2`  
   La **segunda**: en el paso 4 ya se ha atendido P:SUBIR; cuando E:humo llega en el paso 6 se pone delante de todo y sale en el paso 7.
26. `L21`  
   `L21`: llegó la primera y con FIFO se atendía la primera; con prioridad baja al **cuarto** puesto porque todas las pulsaciones y emergencias le adelantan.
27. c) No: adelanta en la cola, pero ningún handler se interrumpe (run-to-completion)  
   La prioridad decide **qué sale de la cola**, no corta lo que se está ejecutando. Cada handler termina entero; E:humo simplemente se coloca la primera para la siguiente vez que el bucle saque un evento.
28. c) Nunca: siempre habría algo más urgente delante (inanición)  
   **Nunca**: con prioridad estricta, un flujo continuo de eventos más urgentes deja sin atender a los de menos prioridad. Se llama **inanición** (*starvation*) y en la sesión de obligatorios veremos cómo evitarla.
29. b) Una función que mete el evento nuevo en la cola  
   Para que se respete run-to-completion, `publicar` debe **encolar** el evento (en la misma cola del bucle). Si llamase al handler sería `PanelDirecto.kt`; si lo ignorase, la emergencia se perdería. Compruébalo en `ColaPanel.kt`.
30. a) La cola separa cuándo ocurre un evento de cuándo se atiende; b) El bucle atiende de uno en uno y hasta el final; d) El orden lo decide la cola  
   Separar ocurrencia y atención, run-to-completion y «el orden lo decide la cola». Ni lo urgente interrumpe, ni el bucle decide el orden: el bucle solo saca y entrega.
31. b) `[L35, E:humo]`  
   Sale `P:MODO` (Pulsado MODO), que no publica nada, y `E:humo` se pone al final: `[L35, E:humo]`. `E:t35` aparecerá cuando se **atienda** `L35`.
32. `L40`  
   Orden: `E:humo` (ALTA), `P:SUBIR` (NORMAL) y `L40` (BAJA). Al atender `L40` se publica `E:t40`, que quedaría en la cola para un cuarto «atender».
33. a) Con cola, quien produce un evento no espera a que se atienda; c) Si un handler encola un evento, ese evento no empieza hasta que el handler termina  
   La cola desacopla productor y handler, y run-to-completion hace que lo encolado espere. La prioridad no interrumpe, y en la actividad el handler y el bucle no cambiaron: solo la cola.

## Bucle de eventos, colas y dispatching

_Las piezas del event loop, por qué la cola absorbe ráfagas pero no sobrecargas, prioridad, coalescencia y descarte, run-to-completion y handlers largos, y cómo se ve todo dentro de Android (Looper, Handler, MessageQueue)._

### La idea de la unidad: la cocina del restaurante · Teoría

La sesión responde a tres preguntas: **de dónde sale la secuencia de eventos, cómo se despachan y qué pasa si un handler tarda**.

| Bloque | Qué se ve |
| --- | --- |
| 1 · Productores, cola y bucle | Las piezas del sistema; orden, prioridad, fusión y descarte. |
| 2 · Run-to-completion | Cada evento termina antes del siguiente; consecuencias de un handler largo. |
| 3 · Android | Looper, Handler y MessageQueue; el presupuesto de 16 ms por fotograma. |

> [!IMPORTANTE] Idea de la unidad
> El handler **no** se ejecuta «cuando ocurre algo»: se ejecuta **cuando le toca**, y eso lo decide la cola.

### La cocina del restaurante

Varios camareros dejan comandas en el **pasador** y **un solo cocinero** las prepara de una en una. Llega la comanda de la mesa 3 y después la de la mesa 7: el cocinero coge la primera, cocina la mesa 3 **entera** (la 7 espera) y luego coge la siguiente.

<div class="cards">
  <div class="card"><h4>Camareros</h4><p>Los <b>productores</b>: toman nota y vuelven a su mesa, sin esperar.</p></div>
  <div class="card"><h4>Pasador</h4><p>La <b>cola</b>: guarda las comandas por orden de llegada.</p></div>
  <div class="card"><h4>Cocinero</h4><p>El <b>bucle</b>: coge una, la termina y coge la siguiente.</p></div>
  <div class="card"><h4>Receta</h4><p>El <b>handler</b>: lo que se hace con cada tipo de comanda.</p></div>
</div>

Quien produce y quien atiende **no se esperan**: el pasador absorbe la diferencia de ritmo entre ellos.

> [!NOTA]
> No es un ejemplo de juguete: es la arquitectura de **Android**, de un **navegador**, de **Node.js** y de un **firmware**. Cambian los nombres, no las piezas.

**Preguntas**

**1.** Según la idea de la unidad, ¿cuándo se ejecuta un handler?

   a) En el instante exacto en que ocurre el evento
   b) Cuando le toca, según lo decida la cola
   c) Cuando el productor termina de producir todos sus eventos
   d) Cuando el handler anterior lo llama

**2.** En la analogía de la cocina, ¿qué pieza del event loop es la **receta**?

   a) El productor
   b) La cola
   c) El bucle
   d) El handler

### Las piezas del sistema · Teoría

Los **productores** generan eventos, la **cola** los guarda, el **bucle** los saca y el **dispatcher** los entrega a su handler.

![Tres productores (pulsador con su ISR, mensaje de red y temporizador) dejan eventos en una cola FIFO con prioridad. Un bucle con dispatcher, recorrido por un único hilo, saca cada evento y consulta una tabla tipo → handler: PULSADOR → alPulsar, MENSAJE → alMensaje, TICK → alTick](/img/pde/tema-3/piezas-bucle.svg "Productores → cola → bucle + dispatcher → handlers")

| Pieza | Responsabilidad | Regla |
| --- | --- | --- |
| **Evento / mensaje** | Tipo + datos propios de ese tipo (+ instante en que ocurrió) | Pequeño e inmutable: es un **aviso**, no un trabajo |
| **Productor** | Detecta algo y encola un evento | **Rápido**: no ejecuta la reacción |
| **Cola** | Guarda los eventos hasta que les toca | Define **orden, prioridad y qué se descarta** |
| **Bucle + dispatcher** | Saca el siguiente evento y elige el handler por su tipo | **Un único hilo** lo recorre |

Algunas consecuencias que conviene tener claras:

- Una **ISR** (rutina de interrupción) de un pulsador es un productor típico: debe ser cortísima, así que solo encola el evento y sale. La reacción la hará después el handler, fuera de la interrupción.
- El evento lleva **datos**, no **trabajo**: «pulsador 4», «sensor 24 C». Lo que hay que hacer con él lo sabe el handler.
- Como un único hilo recorre el bucle, **los handlers nunca se ejecutan a la vez** entre sí.

> [!EXAMEN]
> La tabla pieza → responsabilidad → regla es material de pregunta corta: «¿qué no debe hacer un productor?» (ejecutar la reacción), «¿qué decide la cola?» (orden, prioridad y descarte).

**Preguntas**

**3.** Marca las reglas correctas de las piezas del event loop.

   a) El productor debe ser rápido y no ejecutar la reacción
   b) El evento debe llevar dentro el trabajo que hay que hacer
   c) La cola define orden, prioridad y qué se descarta
   d) Varios hilos recorren el bucle para ir más rápido

**4.** ¿Con qué criterio elige el dispatcher el handler de un evento?

   a) Por su prioridad
   b) Por su tipo
   c) Por el instante en que ocurrió
   d) Por el productor que lo generó

### Sensor sin cola: ¿cuántos se pierden? · Ejercicio

Un sensor de puerta genera **un evento cada 10 ms** y el handler **tarda 25 ms**. No hay cola: si el handler está ocupado cuando llega un evento, no hay sitio donde esperar.

```js
const llegadas = [0, 10, 20, 30, 40]; // ms: un evento cada 10 ms
const duracion = 25;                  // ms que tarda el handler
let libreEn = 0;

for (const t of llegadas) {
  if (t < libreEn) console.log(`t=${t}: PERDIDO (handler ocupado)`);
  else { console.log(`t=${t}: atendido`); libreEn = t + duracion; }
}
```

`libreEn` es el instante en que el handler vuelve a estar libre. Solo se atiende el evento que llega con el handler libre; al atenderlo, el handler queda ocupado otros 25 ms.

Haz la traza tú: para cada `t`, compara con `libreEn` y actualízalo si se atiende.

| t | ¿t < libreEn? | Resultado | libreEn después |
| --- | --- | --- | --- |
| 0 | 0 < 0: no | atendido | 25 |
| 10 | ? | ? | ? |
| 20 | ? | ? | ? |
| 30 | ? | ? | ? |
| 40 | ? | ? | ? |

> [!CUIDADO]
> Fíjate en que el código **no lanza ningún error** ni avisa de nada: lo que se pierde, desaparece en silencio.

**Preguntas**

**5.** ¿Qué eventos se pierden?

   a) t=0
   b) t=10
   c) t=20
   d) t=30
   e) t=40

**6.** ¿Qué pondrías entre el sensor y el handler para no perder eventos, y con qué orden de salida?

   a) Una pila, sacando el último que llegó
   b) Una cola, sacando por orden de llegada (FIFO)
   c) Una variable que guarde solo el último evento
   d) Nada: basta con hacer el handler más rápido que 10 ms

### Una cola entre medias: la pérdida se convierte en espera · Ejercicio

La salida del sensor sin cola fue:

```text
t=0: atendido
t=10: PERDIDO (handler ocupado)
t=20: PERDIDO (handler ocupado)
t=30: atendido
t=40: PERDIDO (handler ocupado)
```

Ahora ponemos una cola entre medias:

```js
const llegadas = [0, 10, 20, 30, 40];
const duracion = 25;
const cola = [...llegadas];
let reloj = 0;

while (cola.length) {
  const t = cola.shift();
  reloj = Math.max(reloj, t);
  console.log(`evento t=${t}: atendido en ${reloj} (espera ${reloj - t} ms)`);
  reloj += duracion;
}
```

```text
evento t=0: atendido en 0 (espera 0 ms)
evento t=10: atendido en 25 (espera 15 ms)
evento t=20: atendido en 50 (espera 30 ms)
evento t=30: atendido en 75 (espera 45 ms)
evento t=40: atendido en 100 (espera 60 ms)
```

1. **Los eventos esperan su turno** en vez de perderse.
2. **Se sacan por orden de llegada**, uno cada vez (`shift` saca el primero).
3. **El precio es la espera**: crece 15 ms por evento, porque el handler tarda 25 ms y el productor genera uno cada 10 ms.

> [!IMPORTANTE]
> La cola **absorbe las ráfagas**, pero no arregla un handler más lento que los productores: la espera crece (0, 15, 30, 45, 60 ms…) y la cola no deja de crecer.

Juega con el periodo del productor y la duración del handler y observa perdidos (sin cola) y esperas (con cola):

> _Aquí hay un simulador interactivo (`sensor-cola`): ábrelo en la web._

**Preguntas**

**7.** Si hubiera un sexto evento en t=50, ¿cuánto esperaría con la cola? (en ms, solo el número)

**8.** Con un evento cada 25 ms y un handler de 25 ms, ¿qué pasa con la cola?

   a) Crece sin parar, igual que antes
   b) Ningún evento espera: cada uno se atiende al llegar
   c) Se pierde uno de cada dos eventos
   d) La espera crece 25 ms por evento

### La cola absorbe ráfagas, no sobrecargas · Teoría

Una cola que se vacía a tiempo es una cola sana; una cola que solo crece es un sistema mal dimensionado.

| Situación | Qué hace la cola | Qué se nota |
| --- | --- | --- |
| **Ráfaga corta**: llegan 5 eventos juntos y luego silencio | Los guarda y el handler los va sacando | Una espera pasajera; la cola vuelve a quedar vacía |
| **Sobrecarga sostenida**: cada 10 ms llega uno y el handler tarda 25 ms | Crece sin parar | La espera aumenta evento a evento: 0, 15, 30, 45, 60 ms… |
| **Cola con límite**: la sobrecarga continúa y se llena | Ya no cabe nada más | Hay que descartar, y decidir qué |

<div class="cards">
  <div class="card"><h4>Cuándo la cola ayuda</h4><p>Cuando el trabajo medio que llega por segundo es menor que lo que el handler puede atender, aunque llegue a golpes: cargas de pantalla, muchos clics seguidos, mensajes de red agrupados.</p></div>
  <div class="card"><h4>Cuándo no arregla nada</h4><p>Cuando el productor es más rápido que el handler de forma permanente. La cola solo aplaza el problema y gasta memoria. Hace falta otro remedio: un handler más rápido, trabajo en paralelo o frenar al productor.</p></div>
</div>

> [!TRUCO] Dos números que vigilar
> - La **longitud de la cola**.
> - La **latencia** de cada evento: cuánto esperó antes de ejecutarse.
>
> Si ambos crecen sin parar, no es una ráfaga: es una sobrecarga.

> [!ANALOGIA]
> El pasador de la cocina aguanta bien el golpe de las 14:00, cuando entran diez mesas a la vez, porque a las 15:30 ya está vacío. Si entrasen diez mesas cada cuarto de hora toda la noche, ningún pasador, por grande que fuera, salvaría al cocinero.

**Preguntas**

**9.** ¿En qué situaciones **sí** ayuda una cola?

   a) Muchos clics seguidos y luego el usuario se queda leyendo
   b) Un sensor que envía cada 10 ms a un handler que tarda 25 ms, día y noche
   c) Mensajes de red que llegan agrupados de vez en cuando
   d) La carga de una pantalla que genera varios eventos de golpe

**10.** ¿Qué indica que un sistema no está sufriendo una ráfaga sino una sobrecarga?

   a) Que la cola tiene más de 5 eventos en algún momento
   b) Que la longitud de la cola y la latencia crecen sin parar
   c) Que algún evento espera más de 0 ms
   d) Que el handler tarda más de 10 ms

### El bucle completo con tabla de handlers (predice) · Ejercicio

Productores que encolan, un bucle que saca por orden y un **dispatcher** que elige el handler por tipo. Todo cabe en unas líneas de C++:

```cpp
#include <cstdio>
#include <queue>

enum Tipo { PULSADOR, TICK, SENSOR, N_TIPOS };
struct Evento { Tipo tipo; int dato; };
using Handler = void (*)(const Evento&);
std::queue<Evento> cola;

void alPulsar(const Evento& e) { std::printf("pulsador %d\n", e.dato); }
void alMedir(const Evento& e)  { std::printf("sensor %d C\n", e.dato); }
void alTick(const Evento& e) {
    std::printf("tick %d\n", e.dato);
    if (e.dato < 2) cola.push({TICK, e.dato + 1});
}

Handler tabla[N_TIPOS] = { alPulsar, alTick, alMedir };

int main() {
    cola.push({TICK, 1}); cola.push({SENSOR, 24}); cola.push({PULSADOR, 4});
    while (!cola.empty()) { Evento e = cola.front(); cola.pop(); tabla[e.tipo](e); }
}
```

Cómo leerlo:

- **El evento es tipo + dato.** `Tipo` es un `enum`, así que `PULSADOR` vale 0, `TICK` 1 y `SENSOR` 2. `N_TIPOS` (3) sirve para dimensionar la tabla.
- **La tabla de handlers** guarda un puntero a función por tipo, en el mismo orden que el `enum`.
- **El bucle** saca el primero (`front` + `pop`) y lo despacha.
- **`alTick` encola** un nuevo tick mientras el dato sea menor que 2.

Antes de mirar la salida en el paso siguiente, haz la traza de la cola en papel.

**Preguntas**

**11.** ¿Qué handler ejecuta `tabla[SENSOR](e)`?

   a) alPulsar
   b) alTick
   c) alMedir
   d) Ninguno: SENSOR no está en la tabla

**12.** Ordena las líneas que imprime el programa.

   Ordena: pulsador 4 · sensor 24 C · tick 1 · tick 2

### Un event loop completo en una línea · Teoría

La salida es:

```text
tick 1
sensor 24 C
pulsador 4
tick 2
```

Tres cosas que observar:

1. **Un handler puede encolar eventos nuevos**, que se colocan al final de la cola. El `tick 2` creado por el primer handler sale **el último**: espera a los que ya estaban.
2. **El dispatcher es una línea**: `tabla[e.tipo](e)`. El bucle no conoce la lógica de ningún evento; solo indexa la tabla.
3. **El evento es tipo + dato**, y el tipo indexa la tabla.

> [!IMPORTANTE]
> Esto **es** un event loop completo. Todo lo demás de la unidad (prioridad, descarte, handlers largos) son **decisiones sobre esta estructura**: qué tipo de cola usar, qué hacer cuando se llena, cuánto puede tardar cada handler.

Compáralo con la barra del bar de la Hora 1: `while (comandas.isNotEmpty())` + `removeFirst` + `preparar` es exactamente este `while (!cola.empty())` + `front/pop` + `tabla[...]`. La diferencia es que aquí, en lugar de un `when` dentro del handler, hay una **tabla de despacho** que elige el handler por tipo.

> [!TRUCO]
> Añadir un tipo de evento nuevo es añadir un valor al `enum`, una función y una entrada en la tabla. El bucle no se toca.

**Preguntas**

**13.** Escribe la expresión del programa que hace de **dispatcher**.

**14.** ¿Por qué `tick 2` sale el último aunque lo creó el primer handler?

   a) Porque los ticks tienen menos prioridad
   b) Porque se encola al final, detrás de los eventos que ya esperaban
   c) Porque `alTick` lo encola con retraso
   d) Porque el bucle atiende los eventos de dos en dos

### Prioridades: cuando lo urgente espera · Teoría

El orden de llegada es justo, pero **no distingue una alarma de un registro**. Supón cuatro eventos de log de 20 ms cada uno en la cola y que llega una alarma:

| Cola | Orden | La alarma se atiende a los |
| --- | --- | --- |
| Solo FIFO | log 1, log 2, log 3, log 4, ALARMA | **80 ms** |
| Con prioridad | ALARMA (prio 9), log 1, log 2, log 3, log 4 (prio 1) | **0 ms** |

![Tres filas. Solo FIFO: cuatro logs de 20 ms dibujados a escala delante de la ALARMA, que empieza a los 80 ms. Con prioridad: la ALARMA (prio 9) se coloca delante de los cuatro logs (prio 1) y se atiende a los 0 ms. Con prioridad y un log ya en marcha: la alarma llega mientras corre el log 1 y se coloca justo detrás de él, delante de los logs 2 a 4; adelanta, pero no interrumpe](/img/pde/tema-3/prioridad-sin-interrupcion.svg "La prioridad adelanta en la cola, pero no corta al handler que ya se ejecuta")

<div class="cards">
  <div class="card"><h4>Cómo funciona</h4><p>Cada evento lleva un nivel. Se inserta delante del primer evento menos urgente; dentro del mismo nivel, FIFO.</p></div>
  <div class="card"><h4>Lo que no hace</h4><p>No interrumpe el handler que ya corre: adelanta en la cola, no cancela lo que se ejecuta (run-to-completion).</p></div>
  <div class="card"><h4>El riesgo</h4><p>Si lo urgente llega sin parar, lo demás nunca se atiende (<b>inanición</b>). Se evita con límites o con <b>envejecimiento</b>.</p></div>
</div>

El **envejecimiento** (*aging*) consiste en ir subiendo la prioridad de un evento cuanto más tiempo lleva esperando, de modo que tarde o temprano adelante a los urgentes. Es lo que le faltaba a la `L21` del panel de la caldera.

> [!NOTA]
> Con 500 logs delante, la alarma de una cola FIFO tardaría **10 s**. En un microcontrolador, las interrupciones tienen **niveles** de prioridad exactamente por esta razón.

> [!EXAMEN]
> Prioridad ≠ interrupción. Una cola con prioridad **adelanta**; no corta al handler en curso. Y prioridad estricta sin remedio ⇒ riesgo de inanición.

**Preguntas**

**15.** Con 500 logs de 20 ms delante en una cola FIFO, ¿cuántos segundos espera la alarma? (solo el número)

**16.** ¿Qué medidas evitan la inanición en una cola con prioridad?

   a) Envejecimiento: subir la prioridad de lo que lleva mucho esperando
   b) Poner límites a lo urgente
   c) Dejar que la alarma interrumpa al handler en curso
   d) Usar una pila en lugar de una cola

### Fusión de eventos: coalescencia · Teoría

Si llegan muchos eventos **de estado** seguidos, basta con el último. Ejemplo con un mapa que se repinta:

| | Cola |
| --- | --- |
| **Sin fusionar** (5 repintados) | mover x=1, mover x=2, tocar marcador, mover x=3, mover x=4 |
| **Fusionando** (3 eventos, mismo resultado final) | mover x=2, tocar marcador, mover x=4 |

![Arriba, la cola sin fusionar con cinco eventos: mover x=1, mover x=2, tocar marcador, mover x=3 y mover x=4. Mover x=1 y mover x=2 se funden en mover x=2; mover x=3 y mover x=4 se funden en mover x=4; tocar marcador queda en medio y corta la fusión. Abajo, la cola fusionada con tres eventos](/img/pde/tema-3/coalescencia.svg "Cada mover se funde con el anterior solo si es el último de la cola: el «tocar» corta la fusión")

<div class="cards">
  <div class="card"><h4>Se funden: eventos de estado</h4><p>«Dónde está el dedo», «cuánto lleva descargado», «cuánto scroll»: el valor nuevo sustituye al anterior, así que el intermedio no aporta nada.</p></div>
  <div class="card"><h4>No se funden: eventos de acción</h4><p>«Pulsar Comprar», «enviar mensaje»: cada uno cuenta. Fundir dos clics sería perder un pedido.</p></div>
</div>

> [!IMPORTANTE] Regla de la fusión
> Un evento nuevo **solo se funde con el último evento de la cola** y **solo si es del mismo tipo**. Si hay otro por medio, el orden se conserva: por eso el «tocar marcador» corta la fusión y `mover x=2` sobrevive.

¿Por qué no fundir `mover x=2` con `mover x=4` saltándose el «tocar»? Porque cambiaría el orden de los hechos: el marcador se tocó cuando el mapa estaba en `x=2`, no en `x=4`.

> [!NOTA]
> En Android esta fusión se hace a mano (`removeMessages()`), como veremos al final.

**Preguntas**

**17.** Con fusión, el bucle aún no ha sacado nada y llegan: scroll 10, scroll 20, clic Comprar, scroll 30, scroll 40, scroll 50. ¿Cuántos eventos quedan en la cola?

**18.** ¿Qué eventos se pueden fusionar?

   a) Progreso de una descarga (porcentaje)
   b) Pulsar «Comprar»
   c) Posición del dedo al arrastrar
   d) Enviar un mensaje de chat

### Cola llena: elegir qué se pierde y contarlo · Teoría

Con memoria limitada, el descarte es inevitable. La decisión es **qué se tira** y que **no pase desapercibido**.

Ejemplo: una cola de **3 posiciones** recibe 5 lecturas seguidas sin que el bucle saque ninguna. Con la política «descartar el más antiguo», las lecturas 1 y 2 son pisadas; en la cola quedan 3, 4 y 5, y el **contador de perdidas vale 2**.

| Política | Qué descarta | Úsala para | Ejemplo |
| --- | --- | --- | --- |
| **Descartar el más antiguo** | El que lleva más tiempo esperando | Estados: solo importa el último valor | Temperatura, GPS |
| **Rechazar el nuevo** | El que intenta entrar | Órdenes que no se pueden saltar ni reordenar | Comandos de un protocolo |
| **Frenar al productor** | Nada: se le hace esperar | No se puede perder nada | Fichero a red (U5) |

Por qué cada una encaja con su ejemplo:

- En una **temperatura** o un **GPS**, la lectura vieja ya no vale: mejor quedarse con la nueva.
- En un **protocolo de comandos**, saltarse una orden intermedia podría dejar al otro extremo en un estado inconsistente: mejor rechazar lo nuevo (y que el emisor lo sepa).
- Al enviar un **fichero por la red** no se puede perder ni un byte: se frena a quien lee el fichero hasta que haya sitio.

> [!CUIDADO]
> Descartar es una **decisión de diseño, no un accidente**. Elige la política y **registra siempre cuánto pierdes**: un descarte silencioso es un fallo escondido (como el sensor sin cola, que perdía 3 de 5 sin avisar).

**Preguntas**

**19.** Una cola de posiciones GPS se llena. ¿Qué política usarías?

   a) Descartar el más antiguo
   b) Rechazar el nuevo
   c) Frenar al productor
   d) Vaciar la cola entera

**20.** Una cola de comandos de un protocolo (que no se pueden saltar ni reordenar) se llena. ¿Qué política usarías?

   a) Descartar el más antiguo
   b) Fusionar los comandos iguales
   c) Rechazar el nuevo
   d) Descartar uno al azar

**21.** Cola de 3 posiciones, política «descartar el más antiguo». Llegan 7 lecturas y el bucle no saca ninguna. ¿Cuánto vale el contador de perdidas?

### Run-to-completion · Teoría

Un handler se ejecuta **entero** antes de que empiece el siguiente: nada lo interrumpe, **ni siquiera un evento urgente**.

![Línea temporal a escala de 0 a 30 ms con tres eventos. A llega en 0 ms y su handler ocupa de 0 a 30 ms. B llega en 10 ms y espera hasta que A termina en 30 ms; entonces se ejecuta. C llega poco después de B y espera detrás de B; se ejecuta cuando B termina. Los handlers nunca se solapan](/img/pde/tema-3/run-to-completion.svg "A corre entero; B y C esperan su turno en la cola")

<div class="cards">
  <div class="card"><h4>Lo que ganamos</h4><p>Los handlers no se pisan entre sí: no necesitan cerrojos para el estado que comparten. El orden de ejecución es predecible y fácil de probar.</p></div>
  <div class="card"><h4>Lo que pagamos</h4><p>Cada evento espera a todo lo que tenga por delante. Un solo handler lento retrasa a todos los demás.</p></div>
</div>

Ya lo has visto dos veces:

- En la barra del bar, el café del desayuno esperó al zumo.
- En el C++, el `tick 2` lo creó el primer handler, pero no empezó hasta que acabaron los que ya estaban en la cola.

> [!ANALOGIA]
> El cocinero no deja una tortilla a medio cuajar porque entre otra comanda. Termina la tortilla. Eso hace que nunca se mezclen dos platos en la misma sartén (no hay cerrojos), pero si alguien pide un cocido de tres horas, todos los demás esperan.

> [!EXAMEN]
> «¿Qué ganamos y qué pagamos con run-to-completion?» Ganamos: sin cerrojos, orden predecible. Pagamos: un handler lento retrasa a todos.

**Preguntas**

**22.** ¿Qué **ganamos** con run-to-completion?

   a) Los handlers no necesitan cerrojos para el estado compartido
   b) Un evento urgente se atiende al instante aunque haya otro handler en marcha
   c) El orden de ejecución es predecible y fácil de probar
   d) Un handler lento no afecta a los demás

**23.** A llega en 0 ms y su handler dura 30 ms; B llega en 10 ms y su handler dura 5 ms. ¿Cuándo termina el handler de B?

   a) 15 ms
   b) 30 ms
   c) 35 ms
   d) 40 ms

### La pantalla que tarda en responder · Ejercicio

Una tienda web: una carga de **100 ms** en el bucle y, mientras, los clics del usuario van llegando. Cada evento tiene su instante de llegada `t` y lo que cuesta su handler.

```js
const eventos = [
  { t: 0,  nombre: "cargarCatalogo", coste: 100 },
  { t: 16, nombre: "clic Comprar",   coste: 2 },
  { t: 32, nombre: "scroll",         coste: 2 },
  { t: 48, nombre: "clic Carrito",   coste: 2 },
];
let reloj = 0;

for (const e of eventos) {
  reloj = Math.max(reloj, e.t);
  console.log(`${e.nombre.padEnd(15)} espera ${reloj - e.t} ms`);
  reloj += e.coste;
}
```

El razonamiento es el mismo que con el sensor: `reloj` es cuándo queda libre el bucle; cada evento empieza en `max(reloj, t)` y espera la diferencia con su llegada.

Haz la traza:

| Evento | Llega (t) | Empieza | Espera |
| --- | --- | --- | --- |
| cargarCatalogo | 0 | 0 | 0 |
| clic Comprar | 16 | ? | ? |
| scroll | 32 | ? | ? |
| clic Carrito | 48 | ? | ? |

> [!NOTA]
> El usuario percibe más de **100 ms** como «va lento», y a los **5 s** Android muestra «la aplicación no responde». ¿Cómo evitarías que la carga bloquee los clics **sin cambiar el bucle**?

**Preguntas**

**24.** ¿Cuántos ms espera «clic Comprar»?

**25.** ¿Cuántos ms espera «clic Carrito»?

### Trocear el trabajo · Ejercicio

La salida del ejemplo anterior fue:

```text
cargarCatalogo  espera 0 ms
clic Comprar    espera 84 ms
scroll          espera 70 ms
clic Carrito    espera 56 ms
```

Los clics cuestan 2 ms, pero esperan 84, 70 y 56 ms porque un handler de 100 ms bloquea el bucle. La solución sin tocar el bucle: **trocear la carga** en 10 trozos de 10 ms. Cada trozo, al terminar, **se vuelve a encolar** y los clics se cuelan entre ellos.

```js
const cola = [
  { t: 0,  nombre: "cargar",       coste: 10, trozos: 10 },
  { t: 16, nombre: "clic Comprar", coste: 2 },
  { t: 32, nombre: "scroll",       coste: 2 },
  { t: 48, nombre: "clic Carrito", coste: 2 },
];
let reloj = 0;

while (cola.length) {
  const e = cola.shift(); reloj = Math.max(reloj, e.t);
  if (!e.trozos) console.log(`${e.nombre.padEnd(13)} espera ${reloj - e.t} ms`);
  reloj += e.coste;
  if (e.trozos > 1) {
    const sig = { ...e, t: reloj, trozos: e.trozos - 1 };
    const i = cola.findIndex((x) => x.t > sig.t);
    cola.splice(i < 0 ? cola.length : i, 0, sig);
  }
}
console.log(`carga terminada en ${reloj} ms`);
```

1. **Cada trozo termina y devuelve el control**: entre dos trozos el bucle atiende otros eventos.
2. **El siguiente trozo se encola** detrás de lo que ya esperaba (se inserta por instante `t`).
3. **El coste es mínimo**.

```text
clic Comprar  espera 4 ms
scroll        espera 0 ms
clic Carrito  espera 6 ms
carga terminada en 106 ms
```

> _Aquí hay un simulador interactivo (`trocear`): ábrelo en la web._
> [!IMPORTANTE]
> Un handler **decide, delega o hace un trozo**. Los clics esperan 0–6 ms (frente a 56–84) y la carga termina casi a la vez.

**Preguntas**

**26.** ¿Por qué la carga troceada termina en 106 ms y no en 100?

   a) Por el coste de reencolar cada trozo
   b) Por los 6 ms de los tres clics atendidos entre trozos
   c) Porque cada trozo espera 0,6 ms al bucle
   d) Porque el último trozo dura 16 ms

**27.** ¿Por qué «clic Comprar» espera 4 ms y no 0?

   a) Porque llega en 16, a mitad del segundo trozo (10–20), y tiene que esperar a que termine
   b) Porque los clics tienen menos prioridad que la carga
   c) Porque el bucle tarda 4 ms en sacar un evento
   d) Porque antes se atiende el scroll

### Cómo se nota un handler largo · Teoría

La misma causa produce **síntomas distintos** según el sistema:

| Síntoma | Qué pasa por dentro | Dónde se nota |
| --- | --- | --- |
| **Latencia** | Los eventos esperan en la cola mientras el handler corre | Un toque tarda en reaccionar |
| **Pérdida** | La cola se llena y se descarta | Lecturas de sensor que faltan |
| **Congelación** | El bucle no vuelve a mirar la cola | Ventana «no responde», ANR en Android |
| **Reinicio** | El watchdog no recibe su señal a tiempo | Un firmware que se resetea solo |

Y tiene **tres salidas**:

<div class="cards">
  <div class="card"><h4>Trocear</h4><p>Hacer un trozo, reencolar el resto y volver (lo que acabas de ver).</p></div>
  <div class="card"><h4>Delegar</h4><p>Pasar el trabajo a otro hilo o tarea y volver con el resultado (U5 y U6).</p></div>
  <div class="card"><h4>Acotar y medir</h4><p>Presupuesto de tiempo por handler y aviso si se supera.</p></div>
</div>

> [!IMPORTANTE]
> La capacidad de respuesta es una propiedad del **peor** handler, no del handler medio. Basta un handler de 3 s entre mil de 1 ms para que la app se congele 3 s.

El handler **no es el sitio del trabajo lento**. Lo que debe hacer es: leer la entrada, actualizar el estado y encolar lo siguiente.

> [!NOTA] El watchdog
> En un microcontrolador, el *watchdog* es un temporizador que reinicia el sistema si el programa no le «da una señal» periódicamente. Si un handler bloquea el bucle demasiado tiempo, nadie da esa señal y el firmware se reinicia.

**Preguntas**

**28.** Un firmware se resetea solo cada vez que procesa un fichero grande dentro de un handler. ¿Qué síntoma es?

   a) Latencia
   b) Pérdida
   c) Congelación
   d) Reinicio por watchdog

**29.** La capacidad de respuesta de un sistema por eventos depende de...

   a) La duración media de los handlers
   b) La duración del peor handler
   c) El número de productores
   d) El tamaño máximo de la cola

### Android: Looper, Handler y MessageQueue · Teoría

El **hilo principal** (UI thread) de una app Android tiene un **Looper** que saca mensajes de su **MessageQueue** y se los da a un **Handler**.

<div class="flow">
  <div><b>Sistema / otros hilos</b><small>toques, vsync · handler.post(…)</small></div>
  <div><b>MessageQueue</b><small>ordenada por hora</small></div>
  <div><b>Looper.loop()</b><small>bucle del hilo</small></div>
  <div><b>Handler</b><small>handleMessage / Runnable</small></div>
</div>

| Pieza genérica | En Android | Detalle |
| --- | --- | --- |
| Evento / mensaje | `Message` (`what`, `arg1`, `obj`) o `Runnable` | Un `Runnable` es un callback listo para ejecutar |
| Cola | `MessageQueue` | Ordenada por **hora de entrega** (`when`), **no por prioridad** |
| Bucle | `Looper.loop()` | Un Looper por hilo; la app arranca con el del hilo principal |
| Dispatcher + handler | `Handler.dispatchMessage()` | Ligado al Looper de un hilo |
| Fusión / descarte | `removeMessages()`, `postAtFrontOfQueue()` | Se hace a mano, como en la coalescencia |

> [!IMPORTANTE]
> Cada `onClick`, cada `onCreate` y cada dibujo se ejecuta **dentro de un handler de este bucle**. Por eso un `onClick` lento congela toda la app.

> [!CUIDADO] postDelayed no espera
> `postDelayed(r, 500)` **no** espera 500 ms: inserta el mensaje en la cola con `when = ahora + 500` y vuelve en el acto. El Looper lo sacará cuando llegue esa hora... **y el hilo esté libre**. Si en ese momento hay un handler largo en marcha, se ejecutará más tarde.

> [!EXAMEN]
> «¿Por qué criterio ordena Android su MessageQueue?» Por `when` (hora de entrega), no por prioridad.

**Preguntas**

**30.** ¿Cómo está ordenada la `MessageQueue` de Android?

   a) Por prioridad del mensaje
   b) Por hora de entrega (`when`)
   c) Por el hilo que publicó el mensaje
   d) Es una pila: sale el último publicado

**31.** ¿Qué clase de Android hace de **bucle de eventos** de un hilo?

**32.** Llamas a `postDelayed(r, 500)` y justo después el hilo principal ejecuta un handler de 2 s. ¿Cuándo se ejecuta `r`?

   a) A los 500 ms exactos, interrumpiendo el handler
   b) Cuando termine el handler de 2 s, porque `r` solo puede salir con el hilo libre
   c) Nunca: el mensaje caduca
   d) Inmediatamente, porque postDelayed bloquea 500 ms y luego ejecuta

### El presupuesto por fotograma · Teoría

A **60 Hz** hay **16,7 ms** entre dos señales *vsync* para atender los eventos y dibujar. Si un handler tarda más, se pierden fotogramas.

| Pantalla | Presupuesto por fotograma | Qué entra en él |
| --- | --- | --- |
| 60 Hz | 16,7 ms | Eventos de entrada + layout + dibujo |
| 90 Hz | 11,1 ms | Lo mismo, con menos margen |
| 120 Hz | 8,3 ms | Cada handler debe durar pocos ms |

El presupuesto es simplemente 1000 ms / frecuencia: 1000 / 60 ≈ 16,7.

### Un onClick de 42 ms

Con vsync cada ~16,7 ms (0, 16, 33, 50, 66 ms…), un `onClick` lento de **42 ms** ocupa el hilo principal durante más de dos fotogramas enteros. Mientras corre, el hilo no puede dibujar, así que se pierden **2 fotogramas** y la animación da un tirón.

![Línea temporal a escala con señales vsync en 0; 16,7; 33,3; 50 y 66,7 ms. En el hilo principal, un onClick ocupa de 0 a 42 ms. Los fotogramas de 16,7 y 33,3 ms caen mientras corre el onClick y se pierden; el de 50 ms, con el hilo ya libre, se dibuja. Una llave marca el presupuesto de 16,7 ms entre dos vsync](/img/pde/tema-3/fotograma-onclick-42ms.svg "Un onClick de 42 ms tapa dos vsync: dos fotogramas perdidos")

> [!IMPORTANTE] Dos umbrales
> - Por encima de **16 ms** (a 60 Hz) el usuario nota el tirón: *jank*.
> - A partir de **~5 s** sin responder, el sistema muestra **ANR** («la aplicación no responde»).

> [!TRUCO]
> El presupuesto no es solo para tu handler: en esos 16,7 ms también tienen que caber el layout y el dibujo. Un handler «de 15 ms» ya es demasiado.

**Preguntas**

**33.** ¿Cuál es el presupuesto por fotograma a 120 Hz? (en ms, con un decimal)

**34.** Un handler del hilo principal tarda 300 ms. ¿Qué nota el usuario en una pantalla de 60 Hz?

   a) Nada, porque es menos de 5 s
   b) Jank: se pierden fotogramas, aunque no llega a ANR
   c) Un ANR inmediato
   d) Un reinicio de la app por watchdog

### El botón que descarga en el hilo principal · Ejercicio

Una app de reparto: al tocar «Actualizar catálogo», la app se queda congelada durante la descarga.

```kotlin
boton.setOnClickListener {
    val datos = descargarCatalogo()   // 3 s de red
    lista.submit(datos)
}
```

1. **El `onClick` corre en el hilo principal.** Durante 3 s no se atiende ningún otro evento.
2. **La lista no se actualiza hasta que acaba.** Ni scroll, ni animaciones, ni más toques.

Aplica lo que llevas visto:

- El `onClick` es un handler del `Looper` principal. Mientras está dentro de `descargarCatalogo()`, el Looper no puede sacar ningún mensaje más de la `MessageQueue`.
- Los toques, el scroll y los vsync siguen llegando… y se acumulan en la cola.
- Con 60 Hz, en 3 s caben unos 180 fotogramas: ninguno se dibuja.

Piensa antes de contestar: **¿qué ve el usuario en esos 3 s? ¿Y si la red tarda 8 s? ¿Dónde debería ejecutarse la descarga y cómo vuelve el resultado a la lista?**

**Preguntas**

**35.** ¿Qué ve el usuario durante los 3 s de descarga?

   a) La app funciona normal y la lista aparece a los 3 s
   b) Una app congelada: no hace scroll, no anima y no responde a los toques
   c) Un mensaje de error de red
   d) La lista vacía, pero puede seguir usando la app

**36.** ¿Y si la red tarda 8 s?

   a) Lo mismo, solo que más rato; el sistema no hace nada
   b) Android cancela la descarga a los 5 s
   c) Se supera el umbral de ~5 s y el sistema puede mostrar un ANR («la aplicación no responde»)
   d) La app se reinicia por watchdog

### Trabajo fuera, resultado dentro · Teoría

La descarga va a **otro hilo**, y el resultado **vuelve al hilo principal publicando en su Handler**.

```kotlin
val principal = Handler(Looper.getMainLooper())

boton.setOnClickListener {
    thread {
        val datos = descargarCatalogo()          // 3 s, sin molestar
        principal.post { lista.submit(datos) }
    }
}
```

1. **Handler ligado al Looper principal.** `Handler(Looper.getMainLooper())` es la «puerta de entrada» a la cola del hilo de la UI.
2. **El trabajo lento sale del bucle.** El `onClick` solo arranca el hilo: termina en microsegundos.
3. **El resultado se encola.** `principal.post { … }` mete un `Runnable` en la `MessageQueue` del hilo principal; cuando le toque, el Looper lo ejecuta y la lista se actualiza **desde el hilo principal, el único que puede tocar la UI**.

![Dos carriles en el tiempo. En el hilo principal, onClick lanza thread { } y termina enseguida; después el hilo sigue atendiendo toques y scroll. En otro hilo, descargarCatalogo() tarda 3 s. Al acabar, principal.post { … } encola el resultado en el hilo principal, donde se ejecuta lista.submit](/img/pde/tema-3/trabajo-fuera-resultado-dentro.svg "El trabajo lento va a otro hilo; el resultado vuelve como un evento más con principal.post")

> [!IMPORTANTE] Dos reglas
> - **Nunca bloquear el hilo principal.**
> - **Nunca tocar la UI desde otro hilo.**

Es la salida «delegar» de la tabla de handlers largos. Y fíjate en que el resultado vuelve **como un evento más**: el patrón productor → cola → bucle → handler aparece de nuevo, con el hilo de descarga como productor.

> [!NOTA]
> Las corrutinas y `async`/`await` (Unidad 6) hacen lo mismo con menos código.

**Preguntas**

**37.** ¿Por qué no se llama a `lista.submit(datos)` directamente dentro de `thread { }`?

   a) Porque sería más lento
   b) Porque solo el hilo principal puede tocar la UI; hay que encolar el cambio en su Looper
   c) Porque `thread` no puede acceder a variables de fuera
   d) Porque `submit` solo acepta Runnables

**38.** Marca lo que es cierto de la solución.

   a) El `onClick` termina casi al instante
   b) La descarga se ejecuta en el hilo principal pero troceada
   c) La actualización de la lista se ejecuta en el hilo principal
   d) Durante la descarga, la app sigue haciendo scroll y atendiendo toques

### Lista de comprobación y puente a la U4 · Teoría

Seis preguntas para **diseñar o depurar** un sistema por eventos:

| # | Pregunta | Respuesta sana |
| --- | --- | --- |
| 1 | ¿Quién produce y qué hace? | Los productores solo encolan: rápido y sin lógica. |
| 2 | ¿Qué orden y qué prioridad? | FIFO, salvo urgencias; sin inanición. |
| 3 | ¿Qué se fusiona o se descarta? | Solo eventos de estado; y se mide lo perdido. |
| 4 | ¿Cuánto dura el peor handler? | Esa es tu latencia de respuesta. |
| 5 | ¿Dónde va el trabajo largo? | Trocear o delegar; nunca en el bucle. |
| 6 | ¿Cuál es el presupuesto? | 16,7 ms por fotograma, o el plazo del sistema. |

> [!TRUCO]
> Úsala como guion cuando te den un enunciado de diseño en el examen: responder a las seis preguntas suele cubrir todo lo que se pide.

### Puente a la Unidad 4

Ya sabemos que los eventos llegan **de uno en uno y en orden**. Pero un mismo evento («pulsar») debe hacer **cosas distintas según el momento**: el mismo botón enciende si está apagado y apaga si está encendido; el mismo «SUBIR» del panel puede subir la temperatura o cambiar de menú.

Eso no se resuelve con más colas ni más prioridades: se modela con **máquinas de estado**, el tema de la U4.

**Preguntas**

**39.** Ordena las seis preguntas de la lista de comprobación.

   Ordena: ¿Cuál es el presupuesto? · ¿Cuánto dura el peor handler? · ¿Dónde va el trabajo largo? · ¿Quién produce y qué hace? · ¿Qué orden y qué prioridad? · ¿Qué se fusiona o se descarta?

**40.** ¿Qué problema deja abierto la U3 para la U4?

   a) Cómo ejecutar varios handlers a la vez
   b) Que un mismo evento debe hacer cosas distintas según el momento
   c) Cómo evitar la inanición
   d) Cómo descargar datos sin bloquear

### Repaso: bucle de eventos, colas y dispatching · Repaso

> [!RESUMEN] Chuleta
> - **Idea**: el handler se ejecuta cuando le toca; lo decide la cola.
> - **Piezas**: evento (tipo + datos, inmutable), productor (rápido, solo encola), cola (orden, prioridad, descarte), bucle + dispatcher (un hilo; `tabla[e.tipo](e)`).
> - **Sin cola** se pierde; **con cola** se espera. La cola absorbe ráfagas, **no sobrecargas**: vigila longitud y latencia.
> - **Prioridad**: se inserta delante del primero menos urgente; adelanta, no interrumpe. Riesgo: inanición → límites o envejecimiento.
> - **Coalescencia**: solo eventos de estado, solo con el último de la cola y del mismo tipo.
> - **Cola llena**: descartar el más antiguo (estados), rechazar el nuevo (órdenes), frenar al productor (no perder nada). Contar siempre lo perdido.
> - **Run-to-completion**: sin cerrojos y predecible; un handler lento retrasa a todos.
> - **Handler largo**: latencia, pérdida, congelación/ANR, reinicio. Remedios: trocear, delegar, acotar y medir.
> - **Android**: `Looper` + `MessageQueue` (por `when`) + `Handler`. 16,7 ms por fotograma a 60 Hz; ANR a ~5 s. Trabajo fuera, resultado dentro con `post`.

| Concepto | En una frase |
| --- | --- |
| Ráfaga | Llega mucho de golpe, pero de media da tiempo |
| Sobrecarga | Llega más de lo que se atiende, siempre |
| Inanición | Lo poco urgente no se atiende nunca |
| Jank | Fotogramas perdidos por un handler > 16 ms |
| ANR | Hilo principal bloqueado ~5 s |

**Preguntas**

**41.** Un sensor produce cada 5 ms y su handler tarda 8 ms de forma permanente. ¿Qué afirmación es correcta?

   a) Una cola suficientemente grande lo resuelve
   b) La cola solo aplaza el problema: hace falta un handler más rápido, paralelismo o frenar al productor
   c) Basta con poner prioridad a los eventos del sensor
   d) Con run-to-completion no se acumulan eventos

**42.** Marca las afirmaciones correctas.

   a) Una cola con prioridad interrumpe al handler en curso si llega algo urgente
   b) Dos eventos «mover» seguidos al final de la cola se pueden fundir
   c) Dos clics en «Comprar» seguidos se pueden fundir
   d) Descartar sin contar lo perdido es un fallo escondido

**43.** ¿Cuál es el presupuesto por fotograma a 60 Hz? (en ms, con un decimal)

**44.** En Android, ¿cómo se actualiza la UI con el resultado de una descarga hecha en otro hilo?

   a) Llamando a la vista directamente desde el hilo de descarga
   b) Publicando un Runnable en un `Handler` ligado al Looper principal (`post`)
   c) Usando `postAtFrontOfQueue` en el hilo de descarga para que se ejecute ahí
   d) Bloqueando el hilo principal hasta que acabe la descarga

### Solucionario

1. b) Cuando le toca, según lo decida la cola  
   Entre que algo ocurre y que se atiende está la cola. El momento de ejecución lo decide ella (orden de llegada, prioridad...), no el instante del suceso.
2. d) El handler  
   La receta es el handler: la lógica concreta para cada tipo. El cocinero (bucle) solo coge comandas y aplica la receta que toque.
3. a) El productor debe ser rápido y no ejecutar la reacción; c) La cola define orden, prioridad y qué se descarta  
   El evento es un aviso pequeño e inmutable (no un trabajo) y el bucle lo recorre **un único hilo**. Productor rápido y cola que decide orden, prioridad y descarte son las reglas correctas.
4. b) Por su tipo  
   El dispatcher mira el **tipo** del evento y elige su handler. La prioridad la usa la cola para decidir el orden de salida, no el dispatcher.
5. b) t=10; c) t=20; e) t=40  
   t=0 se atiende (libre en 25). t=10 y t=20 llegan con el handler ocupado: perdidos. t=30 ≥ 25: atendido (libre en 55). t=40 < 55: perdido. **3 de 5** desaparecen y nadie se entera.
6. b) Una cola, sacando por orden de llegada (FIFO)  
   Una **cola FIFO** da a cada evento un sitio donde esperar su turno. Hacer el handler más rápido también ayudaría, pero sin cola cualquier ráfaga seguiría perdiendo eventos; y una variable con el último valor solo sirve para eventos de estado.
7. `75`  
   El reloj llega a 100 + 25 = 125, así que el evento de t=50 se atiende en 125: espera **75 ms**. La espera sigue creciendo 15 ms por evento.
8. b) Ningún evento espera: cada uno se atiende al llegar  
   Cada evento llega justo cuando el handler queda libre: espera 0 y la cola nunca acumula. La espera crece solo cuando el handler es más lento que el ritmo de llegada. Puedes comprobarlo en el simulador.
9. a) Muchos clics seguidos y luego el usuario se queda leyendo; c) Mensajes de red que llegan agrupados de vez en cuando; d) La carga de una pantalla que genera varios eventos de golpe  
   Las tres situaciones de ráfaga tienen un ritmo medio asumible. El sensor permanente es una **sobrecarga sostenida**: la cola solo aplaza el problema.
10. b) Que la longitud de la cola y la latencia crecen sin parar  
   En una ráfaga la cola sube y luego vuelve a vaciarse. Si longitud y latencia crecen **sin parar**, el handler no da abasto de forma permanente.
11. c) alMedir  
   `SENSOR` es el índice 2 y `tabla[2]` es `alMedir`. Por eso el orden de la tabla tiene que coincidir con el del `enum`.
12. 1. tick 1 → 2. sensor 24 C → 3. pulsador 4 → 4. tick 2  
   La cola empieza con TICK 1, SENSOR 24 y PULSADOR 4. Al atender TICK 1 se encola TICK 2 **al final**, detrás de los que ya estaban.
13. `tabla[e.tipo](e)`  
   `tabla[e.tipo](e)`: indexa la tabla con el tipo del evento y llama al puntero a función obtenido pasándole el evento.
14. b) Porque se encola al final, detrás de los eventos que ya esperaban  
   `std::queue` es FIFO: `push` coloca al final. SENSOR y PULSADOR ya estaban en la cola, así que salen antes que el tick nuevo.
15. `10`  
   500 × 20 ms = 10 000 ms = **10 s**. Con prioridad, 0 ms (si no hay ningún handler en curso).
16. a) Envejecimiento: subir la prioridad de lo que lleva mucho esperando; b) Poner límites a lo urgente  
   Límites y envejecimiento son los remedios citados. Interrumpir al handler rompería run-to-completion y no resuelve la inanición; una pila empeoraría la justicia del orden.
17. `3`  
   scroll 20 sustituye a scroll 10; el clic corta la fusión; scroll 30, 40 y 50 se funden en scroll 50. Quedan **3**: scroll 20, clic Comprar, scroll 50.
18. a) Progreso de una descarga (porcentaje); c) Posición del dedo al arrastrar  
   Progreso y posición son **estado**: solo importa el último valor. Comprar y enviar son **acciones**: fundirlas sería perder un pedido o un mensaje.
19. a) Descartar el más antiguo  
   La posición es un **estado**: solo importa el último valor. Se tira la más antigua.
20. c) Rechazar el nuevo  
   Rechazar el nuevo mantiene íntegra la secuencia ya aceptada; el emisor sabe que su comando no entró. Tirar uno antiguo rompería el orden.
21. `4`  
   Caben 3 (las lecturas 5, 6 y 7); las 4 primeras se pisan. Contador = **4**.
22. a) Los handlers no necesitan cerrojos para el estado compartido; c) El orden de ejecución es predecible y fácil de probar  
   Sin interrupciones no hay carreras entre handlers, y el orden es reproducible. Pero lo urgente **espera** y un handler lento **sí** retrasa a todos: eso es lo que pagamos.
23. c) 35 ms  
   B espera de 10 a 30 ms (20 ms de latencia), se ejecuta de 30 a 35 ms.
24. `84`  
   Llega en 16 y empieza en 100: espera **84 ms**, aunque su handler cuesta solo 2 ms.
25. `56`  
   Empieza en 104 (100 + 2 + 2) y llegó en 48: espera **56 ms**. El scroll espera 70 (empieza en 102, llegó en 32).
26. b) Por los 6 ms de los tres clics atendidos entre trozos  
   100 ms de carga + 3 eventos de 2 ms intercalados = 106 ms. El simulador no cobra nada por reencolar.
27. a) Porque llega en 16, a mitad del segundo trozo (10–20), y tiene que esperar a que termine  
   El segundo trozo corre de 10 a 20 ms y no se interrumpe: el clic llega en 16 y empieza en 20. Trocear acota la espera al tamaño de un trozo, no la elimina.
28. d) Reinicio por watchdog  
   El handler largo impide que el bucle dé la señal al watchdog a tiempo, y este reinicia el sistema.
29. b) La duración del peor handler  
   Cualquier evento puede caer justo detrás del handler más lento. Por eso la latencia de respuesta la marca el **peor** handler.
30. b) Por hora de entrega (`when`)  
   Por `when`. Un `post` normal usa `when = ahora`, y `postDelayed` lo coloca más adelante en el tiempo.
31. `Looper`  
   `Looper`: hay uno por hilo y `Looper.loop()` saca mensajes de la `MessageQueue` y los despacha.
32. b) Cuando termine el handler de 2 s, porque `r` solo puede salir con el hilo libre  
   `r` queda en la cola con `when = ahora + 500`, pero el Looper no puede sacarlo mientras el handler de 2 s se ejecuta. Se ejecuta al terminar este, con retraso.
33. `8,3`  
   1000 ms / 120 ≈ **8,3 ms** para eventos, layout y dibujo.
34. b) Jank: se pierden fotogramas, aunque no llega a ANR  
   300 ms supera con mucho los 16,7 ms: se pierden fotogramas y la app «va lenta». No llega a los ~5 s del ANR.
35. b) Una app congelada: no hace scroll, no anima y no responde a los toques  
   El hilo principal está bloqueado dentro del `onClick`: ni dibuja ni atiende eventos. Todo se queda quieto hasta que la descarga termina.
36. c) Se supera el umbral de ~5 s y el sistema puede mostrar un ANR («la aplicación no responde»)  
   Más de ~5 s sin que el hilo principal responda ⇒ ANR. El usuario puede elegir cerrar la app.
37. b) Porque solo el hilo principal puede tocar la UI; hay que encolar el cambio en su Looper  
   Tocar la UI desde otro hilo está prohibido. `principal.post { … }` convierte la actualización en un mensaje de la cola del hilo principal.
38. a) El `onClick` termina casi al instante; c) La actualización de la lista se ejecuta en el hilo principal; d) Durante la descarga, la app sigue haciendo scroll y atendiendo toques  
   La descarga no se trocea: se **delega** a otro hilo. El hilo principal queda libre durante esos 3 s y solo recibe, al final, el `Runnable` con el resultado.
39. 1. ¿Quién produce y qué hace? → 2. ¿Qué orden y qué prioridad? → 3. ¿Qué se fusiona o se descarta? → 4. ¿Cuánto dura el peor handler? → 5. ¿Dónde va el trabajo largo? → 6. ¿Cuál es el presupuesto?  
   Productor → cola (orden, fusión, descarte) → handler (peor caso, trabajo largo) → presupuesto. Siguen el recorrido de un evento.
40. b) Que un mismo evento debe hacer cosas distintas según el momento  
   La reacción a un evento depende del estado del sistema: eso se modela con máquinas de estado (U4).
41. b) La cola solo aplaza el problema: hace falta un handler más rápido, paralelismo o frenar al productor  
   Es una sobrecarga sostenida: la espera y la cola crecen sin límite. La cola absorbe ráfagas, no sobrecargas.
42. b) Dos eventos «mover» seguidos al final de la cola se pueden fundir; d) Descartar sin contar lo perdido es un fallo escondido  
   La prioridad adelanta pero no interrumpe; los eventos de acción nunca se funden. Fundir estados y contar los descartes son buenas prácticas.
43. `16,7`  
   1000 / 60 ≈ **16,7 ms** para eventos de entrada, layout y dibujo.
44. b) Publicando un Runnable en un `Handler` ligado al Looper principal (`post`)  
   `Handler(Looper.getMainLooper()).post { … }` encola el cambio en el hilo principal, el único que puede tocar la UI.
