Ir al contenido
learn

15. Servicios hospedados y trabajo en segundo plano

Un IHostedService participa en el ciclo de vida del host. BackgroundService aporta el bucle ExecuteAsync, pero el host sigue siendo dueño del inicio, la cancelación y el apagado.

Completa primero inyección de dependencias y opciones y channels. Ya debes comprender los ciclos de vida de servicios, los cancellation tokens y los flujos asíncronos.

El experimento inicia un host, espera barreras explícitas, procesa dos elementos y pide el apagado. No usa Thread.Sleep y cada espera está limitada a cinco segundos.

En .NET 10, todo BackgroundService.ExecuteAsync se ejecuta en un hilo de segundo plano. El trabajo que debe acabar antes de iniciar otros servicios pertenece al constructor, a StartAsync o a IHostedLifecycleService, no a la parte anterior al primer await de ExecuteAsync.

Una cola limitada es un contrato de aplicación

Sección titulada «Una cola limitada es un contrato de aplicación»

El worker consume un Channel<T> limitado. BoundedChannelFullMode.Wait aplica backpressure cuando la cola está llena. Una API real debería esperar WriteAsync; esta prueba pequeña usa TryWrite porque su capacidad y sus dos escrituras son fijas.

AddHostedService registra el worker como singleton. Por tanto, una dependencia scoped se resuelve dentro de un IServiceScope nuevo para cada elemento. Inyectarla en el constructor del worker crearía una dependencia cautiva.

== The host starts one singleton worker and the queue owns the work
startup observed: True
queued results: C major@scope-1 | G major@scope-2
fresh scope per item: True
shutdown cancellation observed: True

La política predeterminada del host .NET 10 detiene la aplicación cuando un BackgroundService lanza una excepción. La prueba fija BackgroundServiceExceptionBehavior.StopHost, libera una barrera de fallo y observa ApplicationStopping:

== A BackgroundService fault stops its host
fault requested host stop: True

Detener el host no es recuperación. Producción aún necesita una política explícita de reintento, dead-letter u operación para trabajo que pueda repetirse con seguridad.

Ventana de terminal
dotnet run --project code/csharp-advanced/Advanced -c Release -- l15

La salida comprobada está en expected/l15.txt.

  1. Sustituye TryWrite por una API asíncrona y prueba que un tercer elemento espera cuando las dos plazas están ocupadas.
  2. Añade un resultado de fallo por elemento sin detener el worker singleton.
  3. Introduce un segundo consumidor e indica qué garantía de orden se pierde.
Soluciones
  1. Devuelve ValueTask, espera Writer.WriteAsync y retén los dos primeros elementos con barreras. Libera una y verifica que la tercera escritura termina; no deduzcas bloqueo de una demora.
  2. Captura la excepción de una operación, completa su TaskCompletionSource con un fallo tipado y continúa el bucle. No trates la cancelación del host como fallo de trabajo.
  3. Usa SingleReader = false y dos workers. Las lecturas siguen FIFO, pero el orden de terminación depende del procesamiento; añade números de secuencia si el consumidor debe reordenar.